do not display notification when it comes from the currently displayed chat #LINQT-2079

do not create account model for each chat, too much listener sending signals

fix crash in ConferenceInfoList #LINQT-2080
fix warnings on endResetModel
fix messages added twice because of displayMore() function called when lUpdate is running
Plain text in sending text area
This commit is contained in:
Gaelle Braud 2025-10-22 14:44:16 +02:00 committed by gaelle
parent 55a54dc10e
commit 8e10decd65
14 changed files with 182 additions and 79 deletions

View file

@ -451,7 +451,7 @@ App *App::getInstance() {
return dynamic_cast<App *>(QApplication::instance());
}
QThread *App::getLinphoneThread() {
Thread *App::getLinphoneThread() {
return App::getInstance()->mLinphoneThread;
}
@ -1436,6 +1436,17 @@ QString App::getSdkVersion() {
#endif
}
ChatGui *App::getCurrentChat() const {
return mCurrentChat;
}
void App::setCurrentChat(ChatGui *chat) {
if (chat != mCurrentChat) {
mCurrentChat = chat;
emit currentChatChanged();
}
}
float App::getScreenRatio() const {
return mScreenRatio;
}

View file

@ -25,6 +25,7 @@
#include "core/account/AccountProxy.hpp"
#include "core/call/CallProxy.hpp"
#include "core/chat/ChatGui.hpp"
#include "core/setting/SettingsCore.hpp"
#include "core/singleapplication/singleapplication.h"
#include "model/cli/CliModel.hpp"
@ -32,6 +33,7 @@
#include "tool/AbstractObject.hpp"
class CallGui;
class ChatGui;
class Thread;
class Notifier;
class QQuickWindow;
@ -46,13 +48,14 @@ class App : public SingleApplication, public AbstractObject {
Q_PROPERTY(QString shortApplicationVersion READ getShortApplicationVersion CONSTANT)
Q_PROPERTY(QString gitBranchName READ getGitBranchName CONSTANT)
Q_PROPERTY(QString sdkVersion READ getSdkVersion CONSTANT)
Q_PROPERTY(ChatGui *currentChat READ getCurrentChat WRITE setCurrentChat NOTIFY currentChatChanged)
public:
App(int &argc, char *argv[]);
~App();
void setSelf(QSharedPointer<App>(me));
static App *getInstance();
static QThread *getLinphoneThread();
static Thread *getLinphoneThread();
Notifier *getNotifier() const;
EventCountNotifier *getEventCountNotifier();
@ -162,6 +165,9 @@ public:
QString getGitBranchName();
QString getSdkVersion();
ChatGui *getCurrentChat() const;
void setCurrentChat(ChatGui *chat);
float getScreenRatio() const;
Q_INVOKABLE void setScreenRatio(float ratio);
@ -186,6 +192,7 @@ signals:
void defaultAccountChanged();
void callsChanged();
void currentDateChanged();
void currentChatChanged();
// void executeCommand(QString command);
private:
@ -203,6 +210,9 @@ private:
QQuickWindow *mMainWindow = nullptr;
QQuickWindow *mCallsWindow = nullptr;
QQuickWindow *mLastActiveWindow = nullptr;
// Holds the current chat displayed in the view
// to know if we need to display the notification
ChatGui *mCurrentChat = nullptr;
QSharedPointer<SettingsCore> mSettings;
QSharedPointer<AccountList> mAccountList;
QSharedPointer<CallList> mCallList;

View file

@ -95,7 +95,7 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
mChatRoomState = LinphoneEnums::fromLinphone(chatRoom->getState());
mIsEncrypted = chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Encrypted);
auto localAccount = ToolModel::findAccount(chatRoom->getLocalAddress());
if (localAccount) mLocalAccount = AccountCore::create(localAccount);
mLocalAddress = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly());
bool associatedAccountHasIMEncryptionMandatory =
localAccount && localAccount->getParams() &&
localAccount->getParams()->getInstantMessagingEncryptionMandatory();
@ -628,8 +628,8 @@ QList<QSharedPointer<ParticipantCore>> ChatCore::getParticipants() const {
return mParticipants;
}
QSharedPointer<AccountCore> ChatCore::getLocalAccount() const {
return mLocalAccount;
QString ChatCore::getLocalAddress() const {
return mLocalAddress;
}
void ChatCore::updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval) {

View file

@ -150,7 +150,7 @@ public:
QVariantList getParticipantsGui() const;
QStringList getParticipantsAddresses() const;
QSharedPointer<AccountCore> getLocalAccount() const;
QString getLocalAddress() const;
void updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval = false);
@ -210,6 +210,7 @@ private:
int mUnreadMessagesCount;
QString mComposingName;
QString mComposingAddress;
QString mLocalAddress;
bool mIsGroupChat = false;
bool mIsEncrypted = false;
bool mIsReadOnly = false;
@ -227,7 +228,6 @@ private:
LinphoneEnums::ChatRoomState mChatRoomState;
std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage;
QSharedPointer<AccountCore> mLocalAccount;
std::shared_ptr<FriendModel> mFriendModel;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
QSharedPointer<SafeConnection<ChatCore, CoreModel>> mCoreModelConnection;

View file

@ -81,14 +81,28 @@ void ChatList::connectItem(QSharedPointer<ChatCore> chat) {
void ChatList::setSelf(QSharedPointer<ChatList> me) {
mModelConnection = SafeConnection<ChatList, CoreModel>::create(me, CoreModel::getInstance());
mModelConnection->makeConnectToCore(&ChatList::lUpdate, [this]() {
beginResetModel();
mList.clear();
if (mIsUpdating) {
connect(this, &ChatList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &ChatList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
mModelConnection->invokeToModel([this]() {
mustBeInLinphoneThread(getClassName());
beginResetModel();
mList.clear();
// Avoid copy to lambdas
QList<QSharedPointer<ChatCore>> *chats = new QList<QSharedPointer<ChatCore>>();
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
if (!currentAccount) return;
if (!currentAccount) {
setIsUpdating(false);
endResetModel();
return;
}
auto linphoneChatRooms = currentAccount->filterChatRooms(Utils::appStringToCoreString(mFilter));
for (auto it : linphoneChatRooms) {
auto model = createChatCore(it);
@ -109,6 +123,7 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
}
add(*chats);
endResetModel();
setIsUpdating(false);
delete chats;
});
});

View file

@ -81,13 +81,6 @@ void EventLogList::connectItem(const QSharedPointer<EventLogCore> &item) {
}
}
void EventLogList::setIsUpdating(bool updating) {
if (mIsUpdating != updating) {
mIsUpdating = updating;
emit isUpdatingChanged();
}
}
void EventLogList::setChatCore(QSharedPointer<ChatCore> core) {
auto updateChatCore = [this](QSharedPointer<ChatCore> core) {
if (mChatCore != core) {
@ -144,31 +137,42 @@ void EventLogList::setDisplayItemsStep(int displayItemsStep) {
}
void EventLogList::displayMore() {
if (!mChatCore) return;
auto chatModel = mChatCore->getModel();
if (!chatModel) return;
mCoreModelConnection->invokeToModel([this, chatModel]() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
int maxSize = chatModel->getHistorySizeEvents();
int totalItemsCount = mList.count();
auto newCount = std::min(totalItemsCount + mDisplayItemsStep, maxSize);
if (newCount <= totalItemsCount) {
return;
}
auto linphoneLogs = chatModel->getHistoryRange(totalItemsCount, newCount);
QList<QSharedPointer<EventLogCore>> *events = new QList<QSharedPointer<EventLogCore>>();
for (auto it : linphoneLogs) {
auto model = EventLogCore::create(it);
events->push_back(model);
}
mCoreModelConnection->invokeToCore([this, events] {
int currentCount = mList.count();
for (auto it = events->end() - 1; it >= events->begin(); --it) {
connectItem(*it);
prepend(*it);
auto loadMoreItems = [this] {
if (!mChatCore) return;
auto chatModel = mChatCore->getModel();
if (!chatModel) return;
mCoreModelConnection->invokeToModel([this, chatModel]() {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
int maxSize = chatModel->getHistorySizeEvents();
int totalItemsCount = mList.count();
auto newCount = std::min(totalItemsCount + mDisplayItemsStep, maxSize);
if (newCount <= totalItemsCount) {
return;
}
auto linphoneLogs = chatModel->getHistoryRange(totalItemsCount, newCount);
QList<QSharedPointer<EventLogCore>> *events = new QList<QSharedPointer<EventLogCore>>();
for (auto it : linphoneLogs) {
auto model = EventLogCore::create(it);
events->push_back(model);
}
mCoreModelConnection->invokeToCore([this, events] {
int currentCount = mList.count();
for (auto it = events->end() - 1; it >= events->begin(); --it) {
connectItem(*it);
prepend(*it);
}
});
});
};
if (mIsUpdating) {
connect(this, &EventLogList::isUpdatingChanged, this, [this, loadMoreItems] {
if (!mIsUpdating) {
disconnect(this, &EventLogList::isUpdatingChanged, this, nullptr);
loadMoreItems();
}
});
});
return;
} else loadMoreItems();
}
void EventLogList::loadMessagesUpTo(std::shared_ptr<linphone::EventLog> event) {
@ -262,6 +266,15 @@ void EventLogList::setSelf(QSharedPointer<EventLogList> me) {
mCoreModelConnection->makeConnectToCore(&EventLogList::lUpdate, [this]() {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
if (mIsUpdating) {
connect(this, &EventLogList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &EventLogList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
beginResetModel();
mList.clear();

View file

@ -48,8 +48,6 @@ public:
void connectItem(const QSharedPointer<EventLogCore> &item);
void disconnectItem(const QSharedPointer<EventLogCore> &item);
void setIsUpdating(bool updating);
void loadMessagesUpTo(std::shared_ptr<linphone::EventLog> event);
void setLastFoundResult(const QSharedPointer<EventLogCore> &eventLog);
@ -75,7 +73,6 @@ signals:
void messageWithFilterFound(int index);
void listAboutToBeReset();
void chatGuiChanged();
void isUpdatingChanged();
void displayItemsStepChanged();
void messagesLoadedUpTo(std::shared_ptr<linphone::EventLog> event);
@ -84,7 +81,6 @@ private:
QSharedPointer<ChatCore> mChatCore;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
QSharedPointer<SafeConnection<EventLogList, CoreModel>> mCoreModelConnection;
bool mIsUpdating = false;
int mDisplayItemsStep = 0;
int mItemsToLoadBeforeSearchResult = 3;
QSharedPointer<EventLogCore> mLastFoundResult;

View file

@ -59,6 +59,16 @@ void ConferenceInfoList::setSelf(QSharedPointer<ConferenceInfoList> me) {
mCoreModelConnection = SafeConnection<ConferenceInfoList, CoreModel>::create(me, CoreModel::getInstance());
mCoreModelConnection->makeConnectToCore(&ConferenceInfoList::lUpdate, [this]() {
if (mIsUpdating) {
connect(this, &ConferenceInfoList::isUpdatingChanged, this, [this] {
if (!mIsUpdating) {
disconnect(this, &ConferenceInfoList::isUpdatingChanged, this, nullptr);
lUpdate();
}
});
return;
}
setIsUpdating(true);
mCoreModelConnection->invokeToModel([this]() {
mustBeInLinphoneThread(getClassName());
beginResetModel();
@ -68,12 +78,14 @@ void ConferenceInfoList::setSelf(QSharedPointer<ConferenceInfoList> me) {
setAccountConnected(defaultAccount && defaultAccount->getState() == linphone::RegistrationState::Ok);
if (!defaultAccount || !mAccountConnected) {
endResetModel();
setIsUpdating(false);
return;
}
std::list<std::shared_ptr<linphone::ConferenceInfo>> conferenceInfos =
defaultAccount->getConferenceInformationList();
if (conferenceInfos.empty()) {
endResetModel();
setIsUpdating(false);
return;
}
items->push_back(nullptr); // Add Dummy conference for today
@ -96,6 +108,7 @@ void ConferenceInfoList::setSelf(QSharedPointer<ConferenceInfoList> me) {
}
updateHaveCurrentDate();
endResetModel();
setIsUpdating(false);
delete items;
});
});
@ -138,9 +151,12 @@ void ConferenceInfoList::setSelf(QSharedPointer<ConferenceInfoList> me) {
auto accountCore = defaultAccount ? AccountCore::create(defaultAccount) : nullptr;
mCoreModelConnection->invokeToCore([this, accountCore] {
mCurrentAccountCore = accountCore;
if (mCurrentAccountCore)
if (mCurrentAccountCore) {
connect(mCurrentAccountCore.get(), &AccountCore::registrationStateChanged, this,
[this] { emit lUpdate(); });
connect(mCurrentAccountCore.get(), &AccountCore::conferenceInformationUpdated, this,
[this] { emit lUpdate(); });
}
});
});
emit lUpdate();

View file

@ -280,7 +280,6 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
auto account = ToolModel::findAccount(call->getToAddress());
if (account) {
auto accountModel = Utils::makeQObject_ptr<AccountModel>(account);
accountModel->setSelf(accountModel);
if (!accountModel->getNotificationsAllowed()) {
lInfo() << log().arg(
"Notifications have been disabled for this account - not creating a notification for incoming call");
@ -295,6 +294,7 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
}
return;
}
accountModel->deleteLater();
}
auto model = CallCore::create(call);
@ -329,6 +329,7 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
QString remoteAddress;
if (messages.size() > 0) {
shared_ptr<linphone::ChatMessage> message = messages.front();
auto receiverAccount = ToolModel::findAccount(message->getToAddress());
if (receiverAccount) {
@ -339,13 +340,13 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
return;
}
auto accountModel = Utils::makeQObject_ptr<AccountModel>(receiverAccount);
accountModel->setSelf(accountModel);
if (!accountModel->getNotificationsAllowed()) {
lInfo() << log().arg(
"Notifications have been disabled for this account - not creating a notification for "
"incoming message");
return;
}
accountModel->deleteLater();
}
auto getMessage = [this, &remoteAddress, &txt](const shared_ptr<linphone::ChatMessage> &message) {
@ -387,14 +388,33 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
txt = tr("new_chat_room_messages");
}
auto chatCore = ChatCore::create(room);
// Play noitification sound
auto settings = SettingsModel::getInstance();
if (settings && !settings->dndEnabled()) {
CoreModel::getInstance()->getCore()->playLocal(
Utils::appStringToCoreString(settings->getChatNotificationSoundPath()));
}
// Accessibility alert
//: New message on chatroom %1
AccessibilityHelper::announceMessage(tr("new_message_alert_accessible_name").arg(chatCore->getTitle()));
// If chat currently displayed, do not display notification
auto currentlyDisplayedChat = App::getInstance()->getCurrentChat();
auto mainWin = App::getInstance()->getMainWindow();
if (currentlyDisplayedChat && mainWin->isActive()) {
auto linphoneCurrent = currentlyDisplayedChat->mCore->getModel()->getMonitor();
if (linphoneCurrent->getIdentifier() == room->getIdentifier()) {
lInfo() << log().arg("Chat is currently displayed in the view, do not show notification");
return;
}
}
auto chatCore = ChatCore::create(room);
App::postCoreAsync([this, txt, chatCore, remoteAddress]() {
mustBeInMainThread(getClassName());
// Accessibility alert
//: New message on chatroom %1
AccessibilityHelper::announceMessage(tr("new_message_alert_accessible_name").arg(chatCore->getTitle()));
QVariantMap map;
map["message"] = txt;
map["remoteAddress"] = remoteAddress;
@ -405,11 +425,6 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
map["chat"] = QVariant::fromValue(chatCore ? new ChatGui(chatCore) : nullptr);
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
});
auto settings = SettingsModel::getInstance();
if (settings && !settings->dndEnabled()) {
CoreModel::getInstance()->getCore()->playLocal(
Utils::appStringToCoreString(settings->getChatNotificationSoundPath()));
}
}
}
/*

View file

@ -93,6 +93,17 @@ public:
endResetModel();
}
// This can be used to wait for the list
// to be updated before updating it again
// (when multiple calls of lUpdate, if beginResetModel has been called, wait
// for endResetModel to be called to call lUpdate again)
void setIsUpdating(bool updating) {
if (mIsUpdating != updating) {
mIsUpdating = updating;
emit isUpdatingChanged();
}
}
template <class T>
void resetData(QList<QSharedPointer<T>> items) {
beginResetModel();
@ -138,6 +149,12 @@ public:
QModelIndex modelIndex = createIndex(index, 0);
emit dataChanged(modelIndex, modelIndex);
}
signals:
void isUpdatingChanged();
protected:
bool mIsUpdating = false;
};
#endif

View file

@ -1672,24 +1672,24 @@ void Utils::openChat(ChatGui *chat) {
smartShowWindow(mainWindow);
if (mainWindow && chat) {
emit chat->mCore->messageOpen();
auto localChatAccount = chat->mCore->getLocalAccount();
auto accountList = App::getInstance()->getAccountList();
auto defaultAccount = accountList->getDefaultAccountCore();
// If multiple accounts, we must switch to the correct account before opening the chatroom, otherwise,
// a chat room corresponding to the wrong account could be added in the chat list
if (localChatAccount && localChatAccount->getIdentityAddress() != defaultAccount->getIdentityAddress()) {
connect(accountList.get(), &AccountList::defaultAccountChanged, accountList.get(),
[localChatAccount, accountList, chat] {
auto defaultAccount = accountList->getDefaultAccountCore();
if (defaultAccount->getIdentityAddress() == localChatAccount->getIdentityAddress()) {
disconnect(accountList.get(), &AccountList::defaultAccountChanged, accountList.get(),
nullptr);
auto localChatAddress = chat->mCore->getLocalAddress();
QMetaObject::invokeMethod(App::getInstance()->getLinphoneThread()->getThreadId(), [localChatAddress, chat] {
auto core = CoreModel::getInstance()->getCore();
std::shared_ptr<linphone::Account> localAccount = ToolModel::findAccount(localChatAddress);
if (!localAccount) return;
auto defaultAccount = core->getDefaultAccount();
if (defaultAccount &&
localAccount->getParams()->getIdentityAddress() != defaultAccount->getParams()->getIdentityAddress()) {
connect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged,
CoreModel::getInstance().get(), [chat] {
QMetaObject::invokeMethod(getMainWindow(), "openChat",
Q_ARG(QVariant, QVariant::fromValue(chat)));
}
});
localChatAccount->lSetDefaultAccount();
} else QMetaObject::invokeMethod(mainWindow, "openChat", Q_ARG(QVariant, QVariant::fromValue(chat)));
});
core->setDefaultAccount(localAccount);
} else {
QMetaObject::invokeMethod(getMainWindow(), "openChat", Q_ARG(QVariant, QVariant::fromValue(chat)));
}
});
}
}

View file

@ -51,12 +51,18 @@ Control.Button {
property real borderWidth: Utils.getSizeWithScreenRatio(1)
property real keyboardFocusedBorderWidth: Utils.getSizeWithScreenRatio(3)
// Image properties
property var contentImageColor: style?.image?.normal || DefaultStyle.main2_600
property var hoveredImageColor: style?.image?.pressed || Qt.darker(contentImageColor, 1.05)
property var checkedImageColor: style?.image?.checked || Qt.darker(contentImageColor, 1.1)
property var pressedImageColor: style?.image?.pressed || Qt.darker(contentImageColor, 1.1)
property var contentImageColor: style?.image? style.image.normal : DefaultStyle.main2_600
property var hoveredImageColor: style?.image? style.image.pressed : Qt.darker(contentImageColor, 1.05)
property var checkedImageColor: style?.image? style.image.checked : Qt.darker(contentImageColor, 1.1)
property var pressedImageColor: style?.image? style.image.pressed : Qt.darker(contentImageColor, 1.1)
icon.source: style?.iconSource || ""
property color colorizationColor: mainItem.checkable && mainItem.checked ? mainItem.checkedImageColor : mainItem.pressed ? mainItem.pressedImageColor : mainItem.hovered ? mainItem.hoveredImageColor : mainItem.contentImageColor
property color colorizationColor: checkable && checked
? checkedImageColor
: pressed
? pressedImageColor
: hovered
? hoveredImageColor
: contentImageColor
// Size properties
spacing: Utils.getSizeWithScreenRatio(5)
property real radius: Math.ceil(height / 2)

View file

@ -142,7 +142,7 @@ Control.Control {
// RectangleTest{anchors.fill: parent}
width: sendingAreaFlickable.width
height: implicitHeight// sendingAreaFlickable.height
textFormat: TextEdit.AutoText
textFormat: TextEdit.PlainText
onTextChanged: {
mainItem.text = text
}

View file

@ -41,6 +41,10 @@ AbstractMainPage {
if (listStackView.depth === 0 || listStackView.currentItem.objectName !== "chatListItem") listStackView.push(chatListItem)
}
}
AppCpp.currentChat = visible ? selectedChatGui : null
}
onVisibleChanged: {
AppCpp.currentChat = visible ? selectedChatGui : null
}
rightPanelStackView.initialItem: currentChatComp