This commit is contained in:
Gaëlle Braud 2025-07-14 18:11:06 +00:00
parent 86bbb41623
commit dce74eb958
16 changed files with 107 additions and 33 deletions

View file

@ -23,6 +23,8 @@
#include "core/chat/message/content/ChatMessageContentGui.hpp" #include "core/chat/message/content/ChatMessageContentGui.hpp"
#include "core/friend/FriendCore.hpp" #include "core/friend/FriendCore.hpp"
#include "core/setting/SettingsCore.hpp" #include "core/setting/SettingsCore.hpp"
#include "model/core/CoreModel.hpp"
#include "model/friend/FriendModel.hpp"
#include "model/tool/ToolModel.hpp" #include "model/tool/ToolModel.hpp"
#include "tool/Utils.hpp" #include "tool/Utils.hpp"
@ -38,7 +40,7 @@ QSharedPointer<ChatCore> ChatCore::create(const std::shared_ptr<linphone::ChatRo
} }
ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObject(nullptr) { ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObject(nullptr) {
lDebug() << "[ChatCore] new" << this; // lDebug() << "[ChatCore] new" << this;
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());
@ -373,6 +375,15 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
mChatModelConnection->invokeToModel( mChatModelConnection->invokeToModel(
[this, index]() { mChatModel->toggleParticipantAdminStatusAtIndex(index); }); [this, index]() { mChatModel->toggleParticipantAdminStatusAtIndex(index); });
}); });
mCoreModelConnection = SafeConnection<ChatCore, CoreModel>::create(me, CoreModel::getInstance());
if (!ToolModel::findFriendByAddress(mPeerAddress))
mCoreModelConnection->makeConnectToModel(&CoreModel::friendCreated,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f); });
mCoreModelConnection->makeConnectToModel(&CoreModel::friendUpdated,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f); });
mCoreModelConnection->makeConnectToModel(&CoreModel::friendRemoved,
[this](std::shared_ptr<linphone::Friend> f) { updateInfo(f, true); });
} }
QDateTime ChatCore::getLastUpdatedTime() const { QDateTime ChatCore::getLastUpdatedTime() const {
@ -644,3 +655,47 @@ ChatCore::buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom)
QList<QSharedPointer<ParticipantCore>> ChatCore::getParticipants() const { QList<QSharedPointer<ParticipantCore>> ChatCore::getParticipants() const {
return mParticipants; return mParticipants;
} }
void ChatCore::updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
auto fAddress = ToolModel::interpretUrl(mPeerAddress);
bool isThisFriend = mFriendModel && updatedFriend == mFriendModel->getFriend();
if (!isThisFriend)
for (auto f : updatedFriend->getAddresses()) {
if (f->weakEqual(fAddress)) {
isThisFriend = true;
break;
}
}
if (isThisFriend) {
if (isRemoval) {
mFriendModel = nullptr;
mFriendModelConnection = nullptr;
}
int capabilities = mChatModel->getCapabilities();
auto chatroom = mChatModel->getMonitor();
auto chatRoomAddress = chatroom->getPeerAddress()->clone();
if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
mTitle = ToolModel::getDisplayName(chatRoomAddress);
mAvatarUri = ToolModel::getDisplayName(chatRoomAddress);
emit titleChanged(mTitle);
emit avatarUriChanged();
} else {
if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
auto participants = chatroom->getParticipants();
if (participants.size() > 0) {
auto peer = participants.front();
if (peer) mTitle = ToolModel::getDisplayName(peer->getAddress()->clone());
mAvatarUri = ToolModel::getDisplayName(peer->getAddress()->clone());
if (participants.size() == 1) {
auto peerAddress = peer->getAddress();
if (peerAddress) mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
}
}
} else if (mChatModel->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) {
mTitle = Utils::coreStringToAppString(chatroom->getSubject());
mAvatarUri = Utils::coreStringToAppString(chatroom->getSubject());
}
}
}
}

View file

@ -33,6 +33,7 @@
#include <linphone++/linphone.hh> #include <linphone++/linphone.hh>
class EventLogCore; class EventLogCore;
class FriendModel;
class ChatCore : public QObject, public AbstractObject { class ChatCore : public QObject, public AbstractObject {
Q_OBJECT Q_OBJECT
@ -140,6 +141,8 @@ public:
QVariantList getParticipantsGui() const; QVariantList getParticipantsGui() const;
QStringList getParticipantsAddresses() const; QStringList getParticipantsAddresses() const;
void updateInfo(const std::shared_ptr<linphone::Friend> &updatedFriend, bool isRemoval = false);
signals: signals:
// used to close all the notifications when one is clicked // used to close all the notifications when one is clicked
void messageOpen(); void messageOpen();
@ -208,7 +211,10 @@ private:
std::shared_ptr<ChatModel> mChatModel; std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage; QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<EventLogCore>> mEventLogList; QList<QSharedPointer<EventLogCore>> mEventLogList;
std::shared_ptr<FriendModel> mFriendModel;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection; QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;
QSharedPointer<SafeConnection<ChatCore, CoreModel>> mCoreModelConnection;
QSharedPointer<SafeConnection<ChatCore, FriendModel>> mFriendModelConnection;
DECLARE_ABSTRACT_OBJECT DECLARE_ABSTRACT_OBJECT
}; };

View file

@ -212,8 +212,6 @@ bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap d
void Notifier::showNotification(QObject *notification, int timeout) { void Notifier::showNotification(QObject *notification, int timeout) {
// Display notification. // Display notification.
QMetaObject::invokeMethod(notification, NotificationShowMethodName, Qt::DirectConnection);
QTimer *timer = new QTimer(notification); QTimer *timer = new QTimer(notification);
timer->setInterval(timeout); timer->setInterval(timeout);
timer->setSingleShot(true); timer->setSingleShot(true);

View file

@ -32,7 +32,7 @@ DEFINE_ABSTRACT_OBJECT(ChatModel)
ChatModel::ChatModel(const std::shared_ptr<linphone::ChatRoom> &chatroom, QObject *parent) ChatModel::ChatModel(const std::shared_ptr<linphone::ChatRoom> &chatroom, QObject *parent)
: ::Listener<linphone::ChatRoom, linphone::ChatRoomListener>(chatroom, parent) { : ::Listener<linphone::ChatRoom, linphone::ChatRoomListener>(chatroom, parent) {
lDebug() << "[ChatModel] new" << this << " / SDKModel=" << chatroom.get(); // lDebug() << "[ChatModel] new" << this << " / SDKModel=" << chatroom.get();
mustBeInLinphoneThread(getClassName()); mustBeInLinphoneThread(getClassName());
auto coreModel = CoreModel::getInstance(); auto coreModel = CoreModel::getInstance();
if (coreModel) if (coreModel)
@ -88,6 +88,14 @@ QString ChatModel::getPeerAddress() const {
return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly()); return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly());
} }
int ChatModel::getCapabilities() const {
return mMonitor->getCapabilities();
}
bool ChatModel::hasCapability(int capability) const {
return mMonitor->hasCapability(capability);
}
std::shared_ptr<linphone::ChatMessage> ChatModel::getLastChatMessage() { std::shared_ptr<linphone::ChatMessage> ChatModel::getLastChatMessage() {
return mMonitor->getLastMessageInHistory(); return mMonitor->getLastMessageInHistory();
} }

View file

@ -42,6 +42,8 @@ public:
QString getTitle(); QString getTitle();
QString getPeerAddress() const; QString getPeerAddress() const;
std::shared_ptr<linphone::ChatMessage> getLastChatMessage(); std::shared_ptr<linphone::ChatMessage> getLastChatMessage();
int getCapabilities() const;
bool hasCapability(int capability) const;
int getUnreadMessagesCount() const; int getUnreadMessagesCount() const;
void markAsRead(); void markAsRead();
std::list<std::shared_ptr<linphone::ChatMessage>> getHistory() const; std::list<std::shared_ptr<linphone::ChatMessage>> getHistory() const;

View file

@ -254,7 +254,7 @@ VariantObject *Utils::haveAccount() {
void Utils::smartShowWindow(QQuickWindow *window) { void Utils::smartShowWindow(QQuickWindow *window) {
if (!window) return; if (!window) return;
if (window->visibility() == QWindow::Maximized) // Avoid to change visibility mode if (window->visibility() == QWindow::Maximized) // Avoid to change visibility mode
window->showNormal(); window->showMaximized();
else window->show(); else window->show();
App::getInstance()->setLastActiveWindow(window); App::getInstance()->setLastActiveWindow(window);
window->raise(); // Raise ensure to get focus on Mac window->raise(); // Raise ensure to get focus on Mac

View file

@ -44,7 +44,7 @@ Rectangle {
Text { Text {
font: Typography.p3 font: Typography.p3
color: DefaultStyle.main2_500main color: DefaultStyle.main2_500main
text: mainItem.friendCore.presenceNote text: mainItem.friendCore?.presenceNote || ""
wrapMode: Text.Wrap wrapMode: Text.Wrap
Layout.fillWidth: true Layout.fillWidth: true
} }

View file

@ -175,7 +175,7 @@ ListView {
id: historyAvatar id: historyAvatar
property var contactObj: UtilsCpp.findFriendByAddress(modelData.core.peerAddress) property var contactObj: UtilsCpp.findFriendByAddress(modelData.core.peerAddress)
contact: contactObj?.value || null contact: contactObj?.value || null
displayNameVal: contact ? "" : modelData.core.avatarUri displayNameVal: contact ? undefined : modelData.core.avatarUri
secured: modelData.core.isEncrypted secured: modelData.core.isEncrypted
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp) Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp) Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)

View file

@ -148,7 +148,7 @@ Control.Control {
bottomPadding: Math.round(19 * DefaultStyle.dp) bottomPadding: Math.round(19 * DefaultStyle.dp)
leftPadding: Math.round(18 * DefaultStyle.dp) leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp) rightPadding: Math.round(18 * DefaultStyle.dp)
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth) Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: DefaultStyle.grey_200 color: DefaultStyle.grey_200

View file

@ -63,6 +63,7 @@ ListView {
onCountChanged: if (atYEnd) { onCountChanged: if (atYEnd) {
positionViewAtEnd() positionViewAtEnd()
} }
onChatChanged: lastItemVisible = false
Button { Button {
visible: !mainItem.lastItemVisible visible: !mainItem.lastItemVisible
@ -95,6 +96,10 @@ ListView {
if (!mainItem.visible) return if (!mainItem.visible) return
mainItem.positionViewAtIndex(index, ListView.End) mainItem.positionViewAtIndex(index, ListView.End)
} }
onModelReset: Qt.callLater(function() {
var index = eventLogProxy.findFirstUnreadIndex()
positionViewAtIndex(index, ListView.End)
})
} }
header: Item { header: Item {
@ -262,7 +267,7 @@ ListView {
footerPositioning: ListView.OverlayFooter footerPositioning: ListView.OverlayFooter
footer: Control.Control { footer: Control.Control {
visible: composeLayout.composingName !== "" visible: composeLayout.composingName !== "" && composeLayout.composingName !== undefined
width: mainItem.width width: mainItem.width
z: mainItem.z + 2 z: mainItem.z + 2
topPadding: Math.round(5 * DefaultStyle.dp) topPadding: Math.round(5 * DefaultStyle.dp)
@ -273,11 +278,11 @@ ListView {
} }
contentItem: RowLayout { contentItem: RowLayout {
id: composeLayout id: composeLayout
property string composingName: mainItem.chat.core.composingName property var composingName: mainItem.chat?.core.composingName
Avatar { Avatar {
Layout.preferredWidth: Math.round(20 * DefaultStyle.dp) Layout.preferredWidth: Math.round(20 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(20 * DefaultStyle.dp) Layout.preferredHeight: Math.round(20 * DefaultStyle.dp)
_address: mainItem.chat.core.composingAddress _address: mainItem.chat?.core.composingAddress
} }
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true

View file

@ -17,7 +17,7 @@ Loader{
property CallGui call: null property CallGui call: null
property bool isConference: false property bool isConference: false
property bool shadowEnabled: true property bool shadowEnabled: true
property string _address: account property var _address: account
? account.core?.identityAddress || "" ? account.core?.identityAddress || ""
: call : call
? call.core.remoteAddress ? call.core.remoteAddress
@ -26,7 +26,7 @@ Loader{
: '' : ''
readonly property string address: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_address) : _address readonly property string address: SettingsCpp.onlyDisplaySipUriUsername ? UtilsCpp.getUsername(_address) : _address
property var displayNameObj: UtilsCpp.getDisplayName(_address) property var displayNameObj: UtilsCpp.getDisplayName(_address)
property string displayNameVal: account && account.core.displayName property var displayNameVal: account && account.core.displayName
? account.core.displayName ? account.core.displayName
: contact && contact.core.fullName : contact && contact.core.fullName
? contact.core.fullName ? contact.core.fullName
@ -152,7 +152,7 @@ Loader{
} }
EffectImage { EffectImage {
id: initialImg id: initialImg
visible: initialItem.initials == '' visible: initialItem.initials === ""
width: stackView.width/2 width: stackView.width/2
height: width height: width
colorizationColor: DefaultStyle.main2_600 colorizationColor: DefaultStyle.main2_600

View file

@ -37,10 +37,10 @@ Window {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
objectName: '__internalWindow' objectName: '__internalWindow'
property bool isFrameLess : false;
property bool showAsTool : false property bool showAsTool : false
// Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool ensure to have the Window on Top and fullscreen independant // Don't use Popup for flags : it could lead to error in geometry. On Mac, Using Tool ensure to have the Window on Top and fullscreen independant
flags: Qt.BypassWindowManagerHint | (showAsTool?Qt.Tool:Qt.WindowStaysOnTopHint) | Qt.Window | Qt.FramelessWindowHint; // flags: Qt.WindowDoesNotAcceptFocus | Qt.BypassWindowManagerHint | (showAsTool?Qt.Tool:Qt.WindowStaysOnTopHint) | Qt.Window | Qt.FramelessWindowHint;
flags: Qt.WindowDoesNotAcceptFocus | Qt.FramelessWindowHint
opacity: 1.0 opacity: 1.0
height: _content[0] != null ? _content[0].height : 0 height: _content[0] != null ? _content[0].height : 0
width: _content[0] != null ? _content[0].width : 0 width: _content[0] != null ? _content[0].width : 0
@ -48,6 +48,7 @@ Window {
Item { Item {
id: content id: content
anchors.fill:parent anchors.fill:parent
focus: false
property var $parent: mainItem property var $parent: mainItem
} }

View file

@ -81,7 +81,7 @@ RowLayout {
Avatar { Avatar {
property var contactObj: mainItem.chat ? UtilsCpp.findFriendByAddress(mainItem.chat?.core.peerAddress) : null property var contactObj: mainItem.chat ? UtilsCpp.findFriendByAddress(mainItem.chat?.core.peerAddress) : null
contact: contactObj?.value || null contact: contactObj?.value || null
displayNameVal: contact ? "" : mainItem.chat.core.avatarUri displayNameVal: contact ? "" : mainItem.chat?.core.avatarUri
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp) Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp) Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
} }
@ -97,7 +97,7 @@ RowLayout {
} }
} }
EffectImage { EffectImage {
visible: mainItem.chat?.core.muted visible: mainItem.chat?.core.muted || false
Layout.preferredWidth: 20 * DefaultStyle.dp Layout.preferredWidth: 20 * DefaultStyle.dp
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 20 * DefaultStyle.dp Layout.preferredHeight: 20 * DefaultStyle.dp
@ -122,7 +122,7 @@ RowLayout {
RoundButton { RoundButton {
style: ButtonStyle.noBackground style: ButtonStyle.noBackground
icon.source: AppIcons.videoCamera icon.source: AppIcons.videoCamera
visible: !mainItem.chat.core.isGroupChat visible: !mainItem.chat?.core.isGroupChat || false
onPressed: mainItem.oneOneCall(true) onPressed: mainItem.oneOneCall(true)
} }
RoundButton { RoundButton {
@ -443,8 +443,8 @@ RowLayout {
} }
ChatDroppableTextArea { ChatDroppableTextArea {
id: messageSender id: messageSender
Control.SplitView.preferredHeight: mainItem.chat.core.isReadOnly ? 0 : height Control.SplitView.preferredHeight: mainItem.chat?.core.isReadOnly ? 0 : height
Control.SplitView.minimumHeight: mainItem.chat.core.isReadOnly ? 0 : Math.round(79 * DefaultStyle.dp) Control.SplitView.minimumHeight: mainItem.chat?.core.isReadOnly ? 0 : Math.round(79 * DefaultStyle.dp)
chat: mainItem.chat chat: mainItem.chat
onChatChanged: { onChatChanged: {
if (chat) messageSender.text = mainItem.chat.core.sendingText if (chat) messageSender.text = mainItem.chat.core.sendingText
@ -513,7 +513,7 @@ RowLayout {
? forwardToListsComponent ? forwardToListsComponent
: panelType === SelectedChatView.PanelType.ManageParticipants : panelType === SelectedChatView.PanelType.ManageParticipants
? manageParticipantsComponent ? manageParticipantsComponent
: mainItem.chat.core.isGroupChat : mainItem.chat?.core.isGroupChat
? groupInfoComponent ? groupInfoComponent
: oneToOneInfoComponent : oneToOneInfoComponent
active: detailsPanel.visible active: detailsPanel.visible

View file

@ -192,6 +192,7 @@ FocusScope {
id: rightPanelStackView id: rightPanelStackView
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
visible: false
} }
} }
} }

View file

@ -25,7 +25,7 @@ AbstractMainPage {
property bool isRegistered: account ? account.core?.registrationState property bool isRegistered: account ? account.core?.registrationState
== LinphoneEnums.RegistrationState.Ok : false == LinphoneEnums.RegistrationState.Ok : false
property var selectedChatGui property var selectedChatGui: null
property string remoteAddress property string remoteAddress
onRemoteAddressChanged: console.log("ChatPage : remote address changed :", remoteAddress) onRemoteAddressChanged: console.log("ChatPage : remote address changed :", remoteAddress)
property var remoteChatObj: UtilsCpp.getChatForAddress(remoteAddress) property var remoteChatObj: UtilsCpp.getChatForAddress(remoteAddress)
@ -40,17 +40,14 @@ AbstractMainPage {
listStackView.popToIndex(0) listStackView.popToIndex(0)
if (listStackView.depth === 0 || listStackView.currentItem.objectName !== "chatListItem") listStackView.push(chatListItem) if (listStackView.depth === 0 || listStackView.currentItem.objectName !== "chatListItem") listStackView.push(chatListItem)
} }
rightPanelStackView.replace(currentChatComp,
Control.StackView.Immediate)
}
else {
rightPanelStackView.replace(emptySelection,
Control.StackView.Immediate)
} }
} }
rightPanelStackView.initialItem: emptySelection rightPanelStackView.initialItem: currentChatComp
rightPanelStackView.visible: listStackView.currentItem && listStackView.currentItem.objectName === "chatListItem" // Only play on the visible property of the right panel as there is only one item pushed
// and the sending TextArea must be instantiated only once, otherwise it looses focus
// when the chat list order is updated
rightPanelStackView.visible: false//listStackView.currentItem && listStackView.currentItem.objectName === "chatListItem" && selectedChatGui !== null
onNoItemButtonPressed: goToNewChat() onNoItemButtonPressed: goToNewChat()
@ -334,10 +331,11 @@ AbstractMainPage {
id: currentChatComp id: currentChatComp
FocusScope { FocusScope {
SelectedChatView { SelectedChatView {
visible: chat != undefined && chat != null
anchors.fill: parent anchors.fill: parent
chat: mainItem.selectedChatGui || null chat: mainItem.selectedChatGui || null
onChatChanged: if (mainItem.selectedChatGui !== chat) mainItem.selectedChatGui = chat onChatChanged: if (mainItem.selectedChatGui !== chat) mainItem.selectedChatGui = chat
} }
} }
} }
} }

View file

@ -28,7 +28,7 @@ AbstractWindow {
property var accountProxy property var accountProxy
// TODO : use this to make the border transparent // TODO : use this to make the border transparent
// flags: Qt.Window | Qt.FramelessWindowHint | Qt.WindowTitleHint flags: Qt.Window | Qt.WindowTitleHint
// menuBar: Rectangle { // menuBar: Rectangle {
// width: parent.width // width: parent.width
// height: Math.round(40 * DefaultStyle.dp) // height: Math.round(40 * DefaultStyle.dp)