open chat when clicking on message notification

select chat by clicking on notification

close all notifications when one clicked and chat is open
This commit is contained in:
Gaelle Braud 2025-05-13 15:46:36 +02:00
parent e178bd43cf
commit 4bb1e5da43
12 changed files with 90 additions and 27 deletions

View file

@ -41,11 +41,14 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership); App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mLastUpdatedTime = QDateTime::fromSecsSinceEpoch(chatRoom->getLastUpdateTime()); mLastUpdatedTime = QDateTime::fromSecsSinceEpoch(chatRoom->getLastUpdateTime());
auto chatRoomAddress = chatRoom->getPeerAddress()->clone();
chatRoomAddress->clean();
mChatRoomAddress = Utils::coreStringToAppString(chatRoomAddress->asStringUriOnly());
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) { if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
mTitle = ToolModel::getDisplayName(chatRoom->getPeerAddress()->clone()); mTitle = ToolModel::getDisplayName(chatRoomAddress);
mAvatarUri = ToolModel::getDisplayName(chatRoom->getPeerAddress()->clone()); mAvatarUri = ToolModel::getDisplayName(chatRoomAddress);
auto peerAddress = chatRoom->getPeerAddress(); mPeerAddress = Utils::coreStringToAppString(chatRoomAddress->asStringUriOnly());
mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly()); mIsGroupChat = false;
} else { } else {
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) { if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
auto participants = chatRoom->getParticipants(); auto participants = chatRoom->getParticipants();
@ -58,9 +61,11 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
if (peerAddress) mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly()); if (peerAddress) mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
} }
} }
mIsGroupChat = false;
} else if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) { } else if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) {
mTitle = Utils::coreStringToAppString(chatRoom->getSubject()); mTitle = Utils::coreStringToAppString(chatRoom->getSubject());
mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject()); mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject());
mIsGroupChat = true;
} }
} }
mUnreadMessagesCount = chatRoom->getUnreadMessagesCount(); mUnreadMessagesCount = chatRoom->getUnreadMessagesCount();
@ -101,7 +106,10 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() { mChatModelConnection->makeConnectToModel(&ChatModel::historyDeleted, [this]() {
mChatModelConnection->invokeToCore([this]() { mChatModelConnection->invokeToCore([this]() {
clearMessagesList(); clearMessagesList();
Utils::showInformationPopup(tr("Supprimé"), tr("L'historique des messages a été supprimé."), true); //: Deleted
Utils::showInformationPopup(tr("info_toast_deleted_title"),
//: Message history has been deleted
tr("info_toast_deleted_message_history"), true);
}); });
}); });
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateUnreadCount, [this]() { mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateUnreadCount, [this]() {
@ -170,7 +178,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr; auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr;
mChatModelConnection->invokeToModel([this, lastMessageModel]() { mChatModelConnection->invokeToModel([this, lastMessageModel]() {
auto linphoneMessage = mChatModel->getLastChatMessage(); auto linphoneMessage = mChatModel->getLastChatMessage();
if (lastMessageModel && lastMessageModel->getMonitor() != linphoneMessage) { if (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage) {
auto chatMessageCore = ChatMessageCore::create(linphoneMessage); auto chatMessageCore = ChatMessageCore::create(linphoneMessage);
mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); }); mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); });
} }
@ -232,6 +240,10 @@ void ChatCore::setTitle(QString title) {
} }
} }
bool ChatCore::isGroupChat() const {
return mIsGroupChat;
}
QString ChatCore::getIdentifier() const { QString ChatCore::getIdentifier() const {
return mIdentifier; return mIdentifier;
} }
@ -240,11 +252,8 @@ QString ChatCore::getPeerAddress() const {
return mPeerAddress; return mPeerAddress;
} }
void ChatCore::setPeerAddress(QString peerAddress) { QString ChatCore::getChatRoomAddress() const {
if (mPeerAddress != peerAddress) { return mChatRoomAddress;
mPeerAddress = peerAddress;
emit peerAddressChanged(peerAddress);
}
} }
QString ChatCore::getAvatarUri() const { QString ChatCore::getAvatarUri() const {
@ -359,4 +368,4 @@ QString ChatCore::getComposingAddress() const {
std::shared_ptr<ChatModel> ChatCore::getModel() const { std::shared_ptr<ChatModel> ChatCore::getModel() const {
return mChatModel; return mChatModel;
} }

View file

@ -36,7 +36,8 @@ class ChatCore : public QObject, public AbstractObject {
public: public:
Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QString identifier READ getIdentifier CONSTANT) Q_PROPERTY(QString identifier READ getIdentifier CONSTANT)
Q_PROPERTY(QString peerAddress READ getPeerAddress WRITE setPeerAddress NOTIFY peerAddressChanged) Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT)
Q_PROPERTY(QString chatRoomAddress READ getChatRoomAddress CONSTANT)
Q_PROPERTY(QString avatarUri READ getAvatarUri WRITE setAvatarUri NOTIFY avatarUriChanged) Q_PROPERTY(QString avatarUri READ getAvatarUri WRITE setAvatarUri NOTIFY avatarUriChanged)
Q_PROPERTY(QDateTime lastUpdatedTime READ getLastUpdatedTime WRITE setLastUpdatedTime NOTIFY lastUpdatedTimeChanged) Q_PROPERTY(QDateTime lastUpdatedTime READ getLastUpdatedTime WRITE setLastUpdatedTime NOTIFY lastUpdatedTimeChanged)
Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged) Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged)
@ -46,7 +47,7 @@ public:
unreadMessagesCountChanged) unreadMessagesCountChanged)
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged) Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged) Q_PROPERTY(QString composingAddress READ getComposingAddress WRITE setComposingAddress NOTIFY composingUserChanged)
// Q_PROPERTY(VideoStats videoStats READ getVideoStats WRITE setVideoStats NOTIFY videoStatsChanged) Q_PROPERTY(bool isGroupChat READ isGroupChat CONSTANT)
// Should be call from model Thread. Will be automatically in App thread after initialization // Should be call from model Thread. Will be automatically in App thread after initialization
static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom); static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
@ -60,6 +61,8 @@ public:
QString getTitle() const; QString getTitle() const;
void setTitle(QString title); void setTitle(QString title);
bool isGroupChat() const;
QString getIdentifier() const; QString getIdentifier() const;
ChatMessageGui *getLastMessage() const; ChatMessageGui *getLastMessage() const;
@ -73,8 +76,8 @@ public:
int getUnreadMessagesCount() const; int getUnreadMessagesCount() const;
void setUnreadMessagesCount(int count); void setUnreadMessagesCount(int count);
QString getChatRoomAddress() const;
QString getPeerAddress() const; QString getPeerAddress() const;
void setPeerAddress(QString peerAddress);
QList<QSharedPointer<ChatMessageCore>> getChatMessageList() const; QList<QSharedPointer<ChatMessageCore>> getChatMessageList() const;
void resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list); void resetChatMessageList(QList<QSharedPointer<ChatMessageCore>> list);
@ -93,11 +96,14 @@ public:
std::shared_ptr<ChatModel> getModel() const; std::shared_ptr<ChatModel> getModel() const;
Q_SIGNALS:
// used to close all the notifications when one is clicked
void messageOpen();
signals: signals:
void lastUpdatedTimeChanged(QDateTime time); void lastUpdatedTimeChanged(QDateTime time);
void lastMessageChanged(); void lastMessageChanged();
void titleChanged(QString title); void titleChanged(QString title);
void peerAddressChanged(QString address);
void unreadMessagesCountChanged(int count); void unreadMessagesCountChanged(int count);
void messageListChanged(); void messageListChanged();
void messagesInserted(QList<QSharedPointer<ChatMessageCore>> list); void messagesInserted(QList<QSharedPointer<ChatMessageCore>> list);
@ -120,12 +126,14 @@ private:
QString id; QString id;
QDateTime mLastUpdatedTime; QDateTime mLastUpdatedTime;
QString mPeerAddress; QString mPeerAddress;
QString mChatRoomAddress;
QString mTitle; QString mTitle;
QString mIdentifier; QString mIdentifier;
QString mAvatarUri; QString mAvatarUri;
int mUnreadMessagesCount; int mUnreadMessagesCount;
QString mComposingName; QString mComposingName;
QString mComposingAddress; QString mComposingAddress;
bool mIsGroupChat = false;
std::shared_ptr<ChatModel> mChatModel; std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage; QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<ChatMessageCore>> mChatMessageList; QList<QSharedPointer<ChatMessageCore>> mChatMessageList;

View file

@ -44,7 +44,7 @@ void ChatProxy::setSourceModel(QAbstractItemModel *model) {
connect(this, &ChatProxy::filterTextChanged, newChatList, connect(this, &ChatProxy::filterTextChanged, newChatList,
[this, newChatList] { emit newChatList->filterChanged(getFilterText()); }); [this, newChatList] { emit newChatList->filterChanged(getFilterText()); });
connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved); connect(newChatList, &ChatList::chatRemoved, this, &ChatProxy::chatRemoved);
// connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); }); connect(newChatList, &ChatList::chatAdded, this, [this] { invalidate(); });
} }
auto firstList = new SortFilterList(model, Qt::AscendingOrder); auto firstList = new SortFilterList(model, Qt::AscendingOrder);
firstList->setDynamicSortFilter(true); firstList->setDynamicSortFilter(true);

View file

@ -33,7 +33,7 @@
#include "core/App.hpp" #include "core/App.hpp"
#include "core/call/CallGui.hpp" #include "core/call/CallGui.hpp"
#include "core/chat/ChatCore.hpp" #include "core/chat/ChatGui.hpp"
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include "tool/LinphoneEnums.hpp" #include "tool/LinphoneEnums.hpp"
#include "tool/providers/AvatarProvider.hpp" #include "tool/providers/AvatarProvider.hpp"
@ -367,11 +367,12 @@ void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom>
mustBeInMainThread(getClassName()); mustBeInMainThread(getClassName());
QVariantMap map; QVariantMap map;
map["message"] = txt; map["message"] = txt;
qDebug() << "create notif from address" << remoteAddress;
map["remoteAddress"] = remoteAddress; map["remoteAddress"] = remoteAddress;
map["chatRoomName"] = chatCore->getTitle(); map["chatRoomName"] = chatCore->getTitle();
map["chatRoomAddress"] = chatCore->getPeerAddress(); map["chatRoomAddress"] = chatCore->getChatRoomAddress();
map["avatarUri"] = chatCore->getAvatarUri(); map["avatarUri"] = chatCore->getAvatarUri();
map["isGroupChat"] = chatCore->isGroupChat();
map["chat"] = QVariant::fromValue(chatCore ? new ChatGui(chatCore) : nullptr);
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map) CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
}); });
} }

View file

@ -1538,6 +1538,7 @@ VariantObject *Utils::getCurrentCallChat(CallGui *call) {
//: Failed to create 1-1 conversation with %1 ! //: Failed to create 1-1 conversation with %1 !
data->mConnection->invokeToCore([] { data->mConnection->invokeToCore([] {
showInformationPopup(tr("information_popup_error_title"), showInformationPopup(tr("information_popup_error_title"),
//: Failed to create 1-1 conversation with %1 !
tr("information_popup_chatroom_creation_error_message"), false, tr("information_popup_chatroom_creation_error_message"), false,
getCallsWindow()); getCallsWindow());
}); });
@ -1583,6 +1584,15 @@ VariantObject *Utils::getChatForAddress(QString address) {
return data; return data;
} }
void Utils::openChat(ChatGui *chat) {
auto mainWindow = getMainWindow();
smartShowWindow(mainWindow);
if (mainWindow && chat) {
emit chat->mCore->messageOpen();
QMetaObject::invokeMethod(mainWindow, "openChat", Q_ARG(QVariant, QVariant::fromValue(chat)));
}
}
bool Utils::isEmptyMessage(QString message) { bool Utils::isEmptyMessage(QString message) {
return message.trimmed().isEmpty(); return message.trimmed().isEmpty();
} }

View file

@ -148,6 +148,7 @@ public:
Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call); Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call);
Q_INVOKABLE static VariantObject *getChatForAddress(QString address); Q_INVOKABLE static VariantObject *getChatForAddress(QString address);
Q_INVOKABLE static void openChat(ChatGui *chat);
Q_INVOKABLE static bool isEmptyMessage(QString message); Q_INVOKABLE static bool isEmptyMessage(QString message);
Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text, Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString &text,
const QVariantMap &options = QVariantMap()); const QVariantMap &options = QVariantMap());

View file

@ -82,8 +82,8 @@ ListView {
// Update position only if we are moving to current item and its position is changing. // Update position only if we are moving to current item and its position is changing.
property var _currentItemY: currentItem?.y property var _currentItemY: currentItem?.y
on_CurrentItemYChanged: if (_currentItemY && moveAnimation.running) { on_CurrentItemYChanged: if (_currentItemY && moveAnimation.running) {
moveToCurrentItem() moveToCurrentItem()
} }
Behavior on contentY { Behavior on contentY {
NumberAnimation { NumberAnimation {
id: moveAnimation id: moveAnimation

View file

@ -14,6 +14,12 @@ ListView {
property color backgroundColor property color backgroundColor
spacing: Math.round(4 * DefaultStyle.dp) spacing: Math.round(4 * DefaultStyle.dp)
onChatChanged: {
var index = chatMessageProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End)
}
Component.onCompleted: { Component.onCompleted: {
var index = chatMessageProxy.findFirstUnreadIndex() var index = chatMessageProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End) positionViewAtIndex(index, ListView.End)
@ -60,6 +66,7 @@ ListView {
chatMessage: modelData chatMessage: modelData
property real maxWidth: Math.round(mainItem.width * (3/4)) property real maxWidth: Math.round(mainItem.width * (3/4))
// height: childrenRect.height // height: childrenRect.height
onVisibleChanged: if (!modelData.core.isRead) modelData.core.lMarkAsRead()
width: mainItem.width width: mainItem.width
property var previousIndex: index - 1 property var previousIndex: index - 1
property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress property var previousFromAddress: chatMessageProxy.getChatMessageAtIndex(index-1)?.core.fromAddress

View file

@ -14,11 +14,22 @@ Notification {
backgroundOpacity: 0.8 backgroundOpacity: 0.8
overriddenWidth: Math.round(400 * DefaultStyle.dp) overriddenWidth: Math.round(400 * DefaultStyle.dp)
overriddenHeight: content.height overriddenHeight: content.height
property var chat: notificationData ? notificationData.chat : null
property string avatarUri: notificationData ? notificationData.avatarUri : "" property string avatarUri: notificationData ? notificationData.avatarUri : ""
property string chatRoomName: notificationData ? notificationData.chatRoomName : "" property string chatRoomName: notificationData ? notificationData.chatRoomName : ""
property string remoteAddress: notificationData ? notificationData.remoteAddress : "" property string remoteAddress: notificationData ? notificationData.remoteAddress : ""
property string chatRoomAddress: notificationData ? notificationData.chatRoomAddress : ""
property bool isGroupChat: notificationData ? notificationData.isGroupChat : false
property string message: notificationData ? notificationData.message : "" property string message: notificationData ? notificationData.message : ""
Connections {
enabled: chat
target: chat.core
function onMessageOpen() {
close()
}
}
Popup { Popup {
id: content id: content
@ -65,14 +76,15 @@ Notification {
icon.height: Math.round(14 * DefaultStyle.dp) icon.height: Math.round(14 * DefaultStyle.dp)
contentImageColor: DefaultStyle.grey_0 contentImageColor: DefaultStyle.grey_0
onPressed: { onPressed: {
mainItem._close() mainItem.close()
} }
} }
MouseArea { MouseArea {
id: mousearea id: mousearea
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
console.log("notif clicked, open chat") UtilsCpp.openChat(mainItem.chat)
mainItem.close()
} }
} }
} }
@ -102,6 +114,7 @@ Notification {
} }
} }
Text { Text {
visible: mainItem.isGroupChat
text: mainItem.remoteAddress text: mainItem.remoteAddress
color: DefaultStyle.main2_100 color: DefaultStyle.main2_100
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -79,6 +79,7 @@ RowLayout {
content: [ content: [
ChatMessagesListView { ChatMessagesListView {
id: chatMessagesListView id: chatMessagesListView
clip: true
height: contentHeight height: contentHeight
backgroundColor: splitPanel.panelColor backgroundColor: splitPanel.panelColor
width: parent.width - anchors.leftMargin - anchors.rightMargin width: parent.width - anchors.leftMargin - anchors.rightMargin
@ -86,7 +87,7 @@ RowLayout {
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: messageSender.top anchors.bottom: messageSender.top
anchors.leftMargin: Math.round(18 * DefaultStyle.dp) anchors.leftMargin: Math.round(18 * DefaultStyle.dp)
anchors.rightMargin: Math.round(18 * DefaultStyle.dp) anchors.rightMargin: Math.round(18 * DefaultStyle.dp)
Control.ScrollBar.vertical: scrollbar Control.ScrollBar.vertical: scrollbar
@ -95,7 +96,7 @@ RowLayout {
id: scrollbar id: scrollbar
visible: chatMessagesListView.contentHeight > parent.height visible: chatMessagesListView.contentHeight > parent.height
active: visible active: visible
anchors.top: parent.top anchors.top: chatMessagesListView.top
anchors.bottom: chatMessagesListView.bottom anchors.bottom: chatMessagesListView.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Math.round(5 * DefaultStyle.dp) anchors.rightMargin: Math.round(5 * DefaultStyle.dp)
@ -214,7 +215,7 @@ RowLayout {
Keys.onPressed: (event) => { Keys.onPressed: (event) => {
event.accepted = false event.accepted = false
if (UtilsCpp.isEmptyMessage(sendingTextArea.text)) return if (UtilsCpp.isEmptyMessage(sendingTextArea.text)) return
if (!(event.modifiers & Qt.ControlModifier) && (event.key == Qt.Key_Return || event.key == Qt.Key_Enter)) { if (!(event.modifiers & Qt.ShiftModifier) && (event.key == Qt.Key_Return || event.key == Qt.Key_Enter)) {
mainItem.chat.core.lSendTextMessage(sendingTextArea.text) mainItem.chat.core.lSendTextMessage(sendingTextArea.text)
sendingTextArea.clear() sendingTextArea.clear()
event.accepted = true event.accepted = true

View file

@ -27,6 +27,7 @@ Item {
signal openNumPadRequest signal openNumPadRequest
signal displayContactRequested(string contactAddress) signal displayContactRequested(string contactAddress)
signal displayChatRequested(string contactAddress) signal displayChatRequested(string contactAddress)
signal openChatRequested(ChatGui chat)
signal createContactRequested(string name, string address) signal createContactRequested(string name, string address)
signal accountRemoved signal accountRemoved
@ -46,6 +47,10 @@ Item {
tabbar.currentIndex = 2 tabbar.currentIndex = 2
mainItem.displayChatRequested(contactAddress) mainItem.displayChatRequested(contactAddress)
} }
function openChat(chat) {
tabbar.currentIndex = 2
mainItem.openChatRequested(chat)
}
function createContact(name, address) { function createContact(name, address) {
tabbar.currentIndex = 1 tabbar.currentIndex = 1
@ -636,6 +641,10 @@ Item {
chatPage.remoteAddress = "" chatPage.remoteAddress = ""
chatPage.remoteAddress = contactAddress chatPage.remoteAddress = contactAddress
} }
function onOpenChatRequested(chat) {
console.log("open chat requested, open", chat.core.title)
chatPage.selectedChatGui = chat
}
} }
} }
MeetingPage {} MeetingPage {}

View file

@ -60,6 +60,10 @@ AbstractWindow {
openMainPage() openMainPage()
mainWindowStackView.currentItem.displayChatPage(contactAddress) mainWindowStackView.currentItem.displayChatPage(contactAddress)
} }
function openChat(chat) {
openMainPage()
mainWindowStackView.currentItem.openChat(chat)
}
function transferCallSucceed() { function transferCallSucceed() {
openMainPage() openMainPage()
//: "Appel transféré" //: "Appel transféré"