open chat room from call/conf

navigate to chat from contact/history/magic search

select chat when joining from contact/history/magic search

message notification

filter chat rooms
This commit is contained in:
Gaelle Braud 2025-04-29 17:30:04 +02:00
parent 8516a3febf
commit b79b324027
43 changed files with 692 additions and 154 deletions

View file

@ -43,17 +43,17 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
mLastUpdatedTime = QDateTime::fromSecsSinceEpoch(chatRoom->getLastUpdateTime());
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
mTitle = ToolModel::getDisplayName(chatRoom->getPeerAddress()->clone());
mAvatarUri = Utils::coreStringToAppString(chatRoom->getPeerAddress()->asStringUriOnly());
mAvatarUri = ToolModel::getDisplayName(chatRoom->getPeerAddress()->clone());
auto peerAddress = chatRoom->getPeerAddress();
mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
} else {
if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne)) {
auto peer = chatRoom->getParticipants().front();
if (peer) mTitle = ToolModel::getDisplayName(peer->getAddress()->clone());
mAvatarUri = Utils::coreStringToAppString(peer->getAddress()->asStringUriOnly());
auto participants = chatRoom->getParticipants();
auto peer = participants.front();
if (peer) mTitle = ToolModel::getDisplayName(peer->getAddress()->clone());
mAvatarUri = ToolModel::getDisplayName(peer->getAddress()->clone());
if (participants.size() == 1) {
auto peerAddress = participants.front()->getAddress();
auto peerAddress = peer->getAddress();
if (peerAddress) mPeerAddress = Utils::coreStringToAppString(peerAddress->asStringUriOnly());
}
} else if (chatRoom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference)) {
@ -75,6 +75,7 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
auto chatMessage = ChatMessageCore::create(message);
mChatMessageList.append(chatMessage);
}
mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier());
}
ChatCore::~ChatCore() {
@ -121,6 +122,10 @@ void ChatCore::setTitle(QString title) {
}
}
QString ChatCore::getIdentifier() const {
return mIdentifier;
}
QString ChatCore::getPeerAddress() const {
return mPeerAddress;
}
@ -193,3 +198,7 @@ void ChatCore::removeMessagesFromMessageList(QList<QSharedPointer<ChatMessageCor
}
if (nbRemoved > 0) emit messageListChanged();
}
std::shared_ptr<ChatModel> ChatCore::getModel() const {
return mChatModel;
}

View file

@ -35,6 +35,7 @@ class ChatCore : public QObject, public AbstractObject {
public:
Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QString identifier READ getIdentifier CONSTANT)
Q_PROPERTY(QString peerAddress READ getPeerAddress WRITE setPeerAddress NOTIFY peerAddressChanged)
Q_PROPERTY(QString avatarUri READ getAvatarUri WRITE setAvatarUri NOTIFY avatarUriChanged)
Q_PROPERTY(QDateTime lastUpdatedTime READ getLastUpdatedTime WRITE setLastUpdatedTime NOTIFY lastUpdatedTimeChanged)
@ -56,6 +57,8 @@ public:
QString getTitle() const;
void setTitle(QString title);
QString getIdentifier() const;
QString getLastMessageInHistory() const;
void setLastMessageInHistory(QString message);
@ -73,6 +76,8 @@ public:
QString getAvatarUri() const;
void setAvatarUri(QString avatarUri);
std::shared_ptr<ChatModel> getModel() const;
signals:
void lastUpdatedTimeChanged(QDateTime time);
void lastMessageInHistoryChanged(QString time);
@ -88,6 +93,7 @@ private:
QString mLastMessageInHistory;
QString mPeerAddress;
QString mTitle;
QString mIdentifier;
QString mAvatarUri;
int mUnreadMessagesCount;
std::shared_ptr<ChatModel> mChatModel;

View file

@ -61,8 +61,7 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
// Avoid copy to lambdas
QList<QSharedPointer<ChatCore>> *chats = new QList<QSharedPointer<ChatCore>>();
auto currentAccount = CoreModel::getInstance()->getCore()->getDefaultAccount();
// auto linphoneChatRooms = currentAccount->filterChatRooms(Utils::appStringToCoreString(mFilter));
auto linphoneChatRooms = currentAccount->getChatRooms();
auto linphoneChatRooms = currentAccount->filterChatRooms(Utils::appStringToCoreString(mFilter));
for (auto it : linphoneChatRooms) {
auto model = createChatCore(it);
chats->push_back(model);
@ -75,25 +74,29 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
});
});
mModelConnection->makeConnectToModel(&CoreModel::chatRoomStateChanged,
[this](const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &chatRoom,
linphone::ChatRoom::State state) {
// check account, filtre, puis ajout si c'est bon
bool toCreate = false;
if (toCreate) {
auto model = createChatCore(chatRoom);
mModelConnection->invokeToCore([this, model]() {
// We set the current here and not on firstChatStarted event
// because we don't want to add unicity check while keeping the
// same model between list and current chat.
add(model);
});
}
});
mModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, [this] (std::shared_ptr<linphone::Core> core, std::shared_ptr<linphone::Account> account) {
lUpdate();
});
mModelConnection->makeConnectToModel(
&CoreModel::chatRoomStateChanged,
[this](const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::ChatRoom> &chatRoom,
linphone::ChatRoom::State state) {
// check account, filtre, puis ajout si c'est bon
if (chatRoom->getAccount() == core->getDefaultAccount()) {
if (state == linphone::ChatRoom::State::Created) {
auto list = getSharedList<ChatCore>();
auto found =
std::find_if(list.begin(), list.end(), [chatRoom](const QSharedPointer<ChatCore> &item) {
return (item && item->getModel()->getMonitor() == chatRoom);
});
if (found != list.end()) {
qDebug() << "chat room created, add it to the list";
auto model = createChatCore(chatRoom);
mModelConnection->invokeToCore([this, model]() { add(model); });
}
}
}
});
mModelConnection->makeConnectToModel(
&CoreModel::defaultAccountChanged,
[this](std::shared_ptr<linphone::Core> core, std::shared_ptr<linphone::Account> account) { lUpdate(); });
connect(this, &ChatList::filterChanged, [this](QString filter) {
mFilter = filter;
@ -102,6 +105,16 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
lUpdate();
}
int ChatList::findChatIndex(ChatGui *chatGui) {
if (!chatGui) return -1;
auto core = chatGui->mCore;
auto chatList = getSharedList<ChatCore>();
auto it = std::find_if(chatList.begin(), chatList.end(), [core](const QSharedPointer<ChatCore> item) {
return item->getIdentifier() == core->getIdentifier();
});
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
QVariant ChatList::data(const QModelIndex &index, int role) const {
int row = index.row();
if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant();

View file

@ -38,9 +38,11 @@ public:
QSharedPointer<ChatCore> createChatCore(const std::shared_ptr<linphone::ChatRoom> &chatroom);
ChatList(QObject *parent = Q_NULLPTR);
~ChatList();
void setSelf(QSharedPointer<ChatList> me);
int findChatIndex(ChatGui *chat);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lUpdate();
void filterChanged(QString filter);

View file

@ -40,15 +40,30 @@ void ChatProxy::setSourceModel(QAbstractItemModel *model) {
}
auto newChatList = dynamic_cast<ChatList *>(model);
if (newChatList) {
// connect(this, &ChatProxy::filterTextChanged, newChatList, &ChatList::filterChanged);
connect(this, &ChatProxy::filterTextChanged, newChatList,
[this, newChatList] { emit newChatList->filterChanged(getFilterText()); });
}
setSourceModels(new SortFilterList(model));
sort(0);
}
int ChatProxy::findChatIndex(ChatGui *chatGui) {
auto chatList = getListModel<ChatList>();
if (chatList) {
auto listIndex = chatList->findChatIndex(chatGui);
if (listIndex != -1) {
listIndex =
dynamic_cast<SortFilterList *>(sourceModel())->mapFromSource(chatList->index(listIndex, 0)).row();
if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep);
return listIndex;
}
}
return -1;
}
bool ChatProxy::SortFilterList::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
// auto l = getItemAtSource<ChatList, ChatCore>(sourceRow);
// return l != nullptr;
// auto l = getItemAtSource<ChatList, ChatCore>(sourceRow);
// return l != nullptr;
return true;
}

View file

@ -39,6 +39,8 @@ public:
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE int findChatIndex(ChatGui *chatGui);
protected:
QSharedPointer<ChatList> mList;
DECLARE_ABSTRACT_OBJECT

View file

@ -20,6 +20,7 @@
#include "ChatMessageCore.hpp"
#include "core/App.hpp"
#include "model/tool/ToolModel.hpp"
DEFINE_ABSTRACT_OBJECT(ChatMessageCore)
@ -31,7 +32,7 @@ QSharedPointer<ChatMessageCore> ChatMessageCore::create(const std::shared_ptr<li
}
ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &chatmessage) {
lDebug() << "[ChatMessageCore] new" << this;
// lDebug() << "[ChatMessageCore] new" << this;
mustBeInLinphoneThread(getClassName());
App::getInstance()->mEngine->setObjectOwnership(this, QQmlEngine::CppOwnership);
mChatMessageModel = Utils::makeQObject_ptr<ChatMessageModel>(chatmessage);
@ -41,6 +42,8 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
auto from = chatmessage->getFromAddress();
auto to = chatmessage->getLocalAddress();
mIsRemoteMessage = !from->weakEqual(to);
mPeerAddress = Utils::coreStringToAppString(chatmessage->getPeerAddress()->asStringUriOnly());
mPeerName = ToolModel::getDisplayName(chatmessage->getPeerAddress()->clone());
}
ChatMessageCore::~ChatMessageCore() {
@ -72,6 +75,14 @@ void ChatMessageCore::setText(QString text) {
}
}
QString ChatMessageCore::getPeerAddress() const {
return mPeerAddress;
}
QString ChatMessageCore::getPeerName() const {
return mPeerName;
}
bool ChatMessageCore::isRemoteMessage() const {
return mIsRemoteMessage;
}

View file

@ -33,6 +33,8 @@ class ChatMessageCore : public QObject, public AbstractObject {
Q_OBJECT
Q_PROPERTY(QDateTime timestamp READ getTimestamp WRITE setTimestamp NOTIFY timestampChanged)
Q_PROPERTY(QString text READ getText WRITE setText NOTIFY textChanged)
Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT)
Q_PROPERTY(QString peerName READ getPeerName CONSTANT)
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage WRITE setIsRemoteMessage NOTIFY isRemoteMessageChanged)
public:
@ -47,6 +49,9 @@ public:
QString getText() const;
void setText(QString text);
QString getPeerAddress() const;
QString getPeerName() const;
bool isRemoteMessage() const;
void setIsRemoteMessage(bool isRemote);
@ -58,6 +63,8 @@ signals:
private:
DECLARE_ABSTRACT_OBJECT
QString mText;
QString mPeerAddress;
QString mPeerName;
QDateTime mTimestamp;
bool mIsRemoteMessage = false;
std::shared_ptr<ChatMessageModel> mChatMessageModel;

View file

@ -33,6 +33,7 @@
#include "core/App.hpp"
#include "core/call/CallGui.hpp"
#include "core/chat/ChatCore.hpp"
#include "model/tool/ToolModel.hpp"
#include "tool/LinphoneEnums.hpp"
#include "tool/providers/AvatarProvider.hpp"
@ -85,9 +86,9 @@ void setProperty(QObject &object, const char *property, const T &value) {
// =============================================================================
const QHash<int, Notifier::Notification> Notifier::Notifications = {
//{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
{Notifier::ReceivedMessage, {Notifier::ReceivedMessage, "NotificationReceivedMessage.qml", 10}},
//{Notifier::ReceivedFileMessage, {Notifier::ReceivedFileMessage, "NotificationReceivedFileMessage.qml", 10}},
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}},
{Notifier::ReceivedCall, {Notifier::ReceivedCall, "NotificationReceivedCall.qml", 30}}
//{Notifier::NewVersionAvailable, {Notifier::NewVersionAvailable, "NotificationNewVersionAvailable.qml", 30}},
//{Notifier::SnapshotWasTaken, {Notifier::SnapshotWasTaken, "NotificationSnapshotWasTaken.qml", 10}},
//{Notifier::RecordingCompleted, {Notifier::RecordingCompleted, "NotificationRecordingCompleted.qml", 10}}
@ -127,7 +128,7 @@ Notifier::~Notifier() {
bool Notifier::createNotification(Notifier::NotificationType type, QVariantMap data) {
mMutex->lock();
Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
// Q_ASSERT(mInstancesNumber <= MaxNotificationsNumber);
if (mInstancesNumber == MaxNotificationsNumber) { // Check existing instances.
qWarning() << QStringLiteral("Unable to create another notification.");
mMutex->unlock();
@ -305,43 +306,68 @@ void Notifier::notifyReceivedCall(const shared_ptr<linphone::Call> &call) {
});
}
/*
void Notifier::notifyReceivedMessages(const list<shared_ptr<linphone::ChatMessage>> &messages) {
QVariantMap map;
QString txt;
if (messages.size() > 0) {
shared_ptr<linphone::ChatMessage> message = messages.front();
void Notifier::notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom> &room,
const list<shared_ptr<linphone::ChatMessage>> &messages) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (messages.size() == 1) {
auto fileContent = message->getFileTransferInformation();
if (!fileContent) {
foreach (auto content, message->getContents()) {
if (content->isText()) txt += content->getUtf8Text().c_str();
}
} else if (fileContent->isVoiceRecording())
//: 'Voice message received!' : message to warn the user in a notofication for voice messages.
txt = tr("new_voice_message");
else txt = tr("new_file_message");
if (txt.isEmpty() && message->hasConferenceInvitationContent())
//: 'Conference invitation received!' : Notification about receiving an invitation to a conference.
txt = tr("new_conference_invitation");
} else
//: 'New messages received!' Notification that warn the user of new messages.
txt = tr("new_chat_room_messages");
map["message"] = txt;
shared_ptr<linphone::ChatRoom> chatRoom(message->getChatRoom());
map["timelineModel"].setValue(
CoreManager::getInstance()->getTimelineListModel()->getTimeline(chatRoom, true).get());
if (messages.size() == 1) { // Display only sender on mono message.
map["remoteAddress"] = Utils::coreStringToAppString(message->getFromAddress()->asStringUriOnly());
map["fullremoteAddress"] = Utils::coreStringToAppString(message->getFromAddress()->asString());
}
map["localAddress"] = Utils::coreStringToAppString(message->getToAddress()->asStringUriOnly());
map["fullLocalAddress"] = Utils::coreStringToAppString(message->getToAddress()->asString());
map["window"].setValue(App::getInstance()->getMainWindow());
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
}
QString txt;
QString remoteAddress;
if (messages.size() > 0) {
shared_ptr<linphone::ChatMessage> message = messages.front();
auto receiverAccount = ToolModel::findAccount(message->getToAddress());
if (receiverAccount) {
auto senderAccount = ToolModel::findAccount(message->getFromAddress());
if (senderAccount) {
return;
}
auto accountModel = Utils::makeQObject_ptr<AccountModel>(receiverAccount);
accountModel->setSelf(accountModel);
if (!accountModel->getNotificationsAllowed()) {
qInfo() << "Notifications have been disabled for this account - not creating a notification for "
"incoming message";
return;
}
}
if (messages.size() == 1) { // Display only sender on mono message.
auto remoteAddr = message->getFromAddress()->clone();
remoteAddr->clean();
remoteAddress = Utils::coreStringToAppString(remoteAddr->asStringUriOnly());
auto fileContent = message->getFileTransferInformation();
if (!fileContent) {
foreach (auto content, message->getContents()) {
if (content->isText()) txt += content->getUtf8Text().c_str();
}
} else if (fileContent->isVoiceRecording())
//: 'Voice message received!' : message to warn the user in a notofication for voice messages.
txt = tr("new_voice_message");
else txt = tr("new_file_message");
if (txt.isEmpty() && message->hasConferenceInvitationContent())
//: 'Conference invitation received!' : Notification about receiving an invitation to a conference.
txt = tr("new_conference_invitation");
} else {
//: 'New messages received!' Notification that warn the user of new messages.
txt = tr("new_chat_room_messages");
}
auto chatCore = ChatCore::create(room);
App::postCoreAsync([this, txt, chatCore, remoteAddress]() {
mustBeInMainThread(getClassName());
QVariantMap map;
map["message"] = txt;
qDebug() << "create notif from address" << remoteAddress;
map["remoteAddress"] = remoteAddress;
map["chatRoomName"] = chatCore->getTitle();
map["chatRoomAddress"] = chatCore->getPeerAddress();
map["avatarUri"] = chatCore->getAvatarUri();
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
});
}
}
/*
void Notifier::notifyReceivedReactions(
const QList<QPair<std::shared_ptr<linphone::ChatMessage>, std::shared_ptr<const linphone::ChatMessageReaction>>>

View file

@ -41,9 +41,9 @@ public:
~Notifier();
enum NotificationType {
// ReceivedMessage,
ReceivedMessage,
// ReceivedFileMessage,
ReceivedCall,
ReceivedCall
// NewVersionAvailable,
// SnapshotWasTaken,
// RecordingCompleted
@ -52,8 +52,9 @@ public:
// void notifyReceivedCall(Call *call);
void notifyReceivedCall(const std::shared_ptr<linphone::Call> &call); // Call from Linphone
void notifyReceivedMessages(const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
/*
void notifyReceivedMessages(const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
void notifyReceivedReactions(
const QList<QPair<std::shared_ptr<linphone::ChatMessage>, std::shared_ptr<const
linphone::ChatMessageReaction>>> &reactions); void notifyReceivedFileMessage(const

View file

@ -110,11 +110,13 @@ int MagicSearchProxy::loadUntil(const QString &address) {
auto magicSearchList = getListModel<MagicSearchList>();
if (magicSearchList) {
auto listIndex = magicSearchList->findFriendIndexByAddress(address);
if (listIndex == -1) return -1;
listIndex =
dynamic_cast<SortFilterList *>(sourceModel())->mapFromSource(magicSearchList->index(listIndex, 0)).row();
if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep);
return listIndex;
if (listIndex != -1) {
listIndex = dynamic_cast<SortFilterList *>(sourceModel())
->mapFromSource(magicSearchList->index(listIndex, 0))
.row();
if (mMaxDisplayItems <= listIndex) setMaxDisplayItems(listIndex + mDisplayItemsStep);
return listIndex;
}
}
return -1;
}

View file

@ -233,6 +233,10 @@ bool CallModel::getZrtpCaseMismatch() const {
return mMonitor->getZrtpCacheMismatchFlag();
}
std::shared_ptr<linphone::Conference> CallModel::getConference() const {
return mMonitor->getConference();
}
void CallModel::setConference(const std::shared_ptr<linphone::Conference> &conference) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
if (mConference != conference) {
@ -411,7 +415,7 @@ void CallModel::onStateChanged(const std::shared_ptr<linphone::Call> &call,
emit localVideoEnabledChanged(videoDirection == linphone::MediaDirection::SendOnly ||
videoDirection == linphone::MediaDirection::SendRecv);
emit remoteVideoEnabledChanged(remoteVideoDirection == linphone::MediaDirection::SendOnly ||
remoteVideoDirection == linphone::MediaDirection::SendRecv);
remoteVideoDirection == linphone::MediaDirection::SendRecv);
updateConferenceVideoLayout();
} else if (state == linphone::Call::State::End || state == linphone::Call::State::Error) {
mDurationTimer.stop();

View file

@ -72,6 +72,8 @@ public:
QStringList getRemoteAtuhenticationTokens() const;
bool getZrtpCaseMismatch() const;
std::shared_ptr<linphone::Conference> getConference() const;
LinphoneEnums::ConferenceLayout getConferenceVideoLayout() const;
void changeConferenceVideoLayout(LinphoneEnums::ConferenceLayout layout); // Make a call request
void updateConferenceVideoLayout(); // Called from call state changed ater the new layout has been set.

View file

@ -54,6 +54,10 @@ std::list<std::shared_ptr<linphone::ChatMessage>> ChatModel::getHistory() const
return res;
}
QString ChatModel::getIdentifier() const {
return Utils::coreStringToAppString(mMonitor->getIdentifier());
}
QString ChatModel::getTitle() {
if (mMonitor->hasCapability((int)linphone::ChatRoom::Capabilities::Basic)) {
return ToolModel::getDisplayName(mMonitor->getPeerAddress()->clone());
@ -65,6 +69,7 @@ QString ChatModel::getTitle() {
return Utils::coreStringToAppString(mMonitor->getSubject());
}
}
return QString();
}
QString ChatModel::getPeerAddress() const {
@ -101,15 +106,14 @@ void ChatModel::onIsComposingReceived(const std::shared_ptr<linphone::ChatRoom>
emit isComposingReceived(chatRoom, remoteAddress, isComposing);
}
// Do not use this api, only manipulate EventLogs
void ChatModel::onMessageReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<linphone::ChatMessage> &message) {
// emit messageReceived(chatRoom, message);
emit messageReceived(chatRoom, message);
}
void ChatModel::onMessagesReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::list<std::shared_ptr<linphone::ChatMessage>> &chatMessages) {
// emit messagesReceived(chatRoom, chatMessages);
emit messagesReceived(chatRoom, chatMessages);
}
void ChatModel::onNewEvent(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
@ -258,5 +262,5 @@ void ChatModel::onChatRoomRead(const std::shared_ptr<linphone::ChatRoom> &chatRo
void ChatModel::onNewMessageReaction(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) {
emit onNewMessageReaction(chatRoom, message, reaction);
// emit onNewMessageReaction(chatRoom, message, reaction);
}

View file

@ -43,6 +43,7 @@ public:
QString getLastMessageInHistory(std::list<std::shared_ptr<linphone::Content>> startList = {}) const;
int getUnreadMessagesCount() const;
std::list<std::shared_ptr<linphone::ChatMessage>> getHistory() const;
QString getIdentifier() const;
private:
DECLARE_ABSTRACT_OBJECT

View file

@ -32,7 +32,7 @@ DEFINE_ABSTRACT_OBJECT(ChatMessageModel)
ChatMessageModel::ChatMessageModel(const std::shared_ptr<linphone::ChatMessage> &chatMessage, QObject *parent)
: ::Listener<linphone::ChatMessage, linphone::ChatMessageListener>(chatMessage, parent) {
lDebug() << "[ChatMessageModel] new" << this << " / SDKModel=" << chatMessage.get();
// lDebug() << "[ChatMessageModel] new" << this << " / SDKModel=" << chatMessage.get();
mustBeInLinphoneThread(getClassName());
}
@ -44,6 +44,10 @@ QString ChatMessageModel::getText() const {
return ToolModel::getMessageFromContent(mMonitor->getContents());
}
QString ChatMessageModel::getPeerAddress() const {
return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly());
}
QDateTime ChatMessageModel::getTimestamp() const {
return QDateTime::fromSecsSinceEpoch(mMonitor->getTime());
}

View file

@ -40,6 +40,8 @@ public:
QString getText() const;
QDateTime getTimestamp() const;
QString getPeerAddress() const;
private:
DECLARE_ABSTRACT_OBJECT
virtual std::shared_ptr<linphone::Buffer> onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,

View file

@ -76,6 +76,10 @@ int ConferenceModel::getParticipantDeviceCount() const {
return mMonitor->getParticipantDeviceList().size();
}
std::shared_ptr<linphone::ChatRoom> ConferenceModel::getChatRoom() const {
return mMonitor->getChatRoom();
}
void ConferenceModel::setMicrophoneMuted(bool isMuted) {
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
mMonitor->setMicrophoneMuted(isMuted);
@ -171,7 +175,8 @@ bool ConferenceModel::isScreenSharingEnabled() const {
void ConferenceModel::onActiveSpeakerParticipantDevice(
const std::shared_ptr<linphone::Conference> &conference,
const std::shared_ptr<const linphone::ParticipantDevice> &participantDevice) {
lDebug() << "onActiveSpeakerParticipantDevice: " << (participantDevice ? participantDevice->getAddress()->asString().c_str() : "NULL");
lDebug() << "onActiveSpeakerParticipantDevice: "
<< (participantDevice ? participantDevice->getAddress()->asString().c_str() : "NULL");
emit activeSpeakerParticipantDevice(conference, conference->getActiveSpeakerParticipantDevice());
}

View file

@ -57,6 +57,8 @@ public:
void removeParticipant(const std::shared_ptr<linphone::Address> &address);
void addParticipant(const std::shared_ptr<linphone::Address> &address);
std::shared_ptr<linphone::ChatRoom> getChatRoom() const;
int getParticipantDeviceCount() const;
void onIsScreenSharingEnabledChanged();

View file

@ -122,9 +122,8 @@ void CoreModel::start() {
linphoneSearch->setLimitedSearch(true);
mMagicSearch = Utils::makeQObject_ptr<MagicSearchModel>(linphoneSearch);
mMagicSearch->setSelf(mMagicSearch);
connect(mMagicSearch.get(), &MagicSearchModel::searchResultsReceived, this, [this] {
emit magicSearchResultReceived(mMagicSearch->mLastSearch);
});
connect(mMagicSearch.get(), &MagicSearchModel::searchResultsReceived, this,
[this] { emit magicSearchResultReceived(mMagicSearch->mLastSearch); });
}
// -----------------------------------------------------------------------------
@ -352,10 +351,11 @@ void CoreModel::migrate() {
config->setInt(SettingsModel::UiSection, Constants::RcVersionName, Constants::RcVersionCurrent);
}
void CoreModel::searchInMagicSearch(QString filter, int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults) {
mMagicSearch->search(filter, sourceFlags, aggregation, maxResults);
void CoreModel::searchInMagicSearch(QString filter,
int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults) {
mMagicSearch->search(filter, sourceFlags, aggregation, maxResults);
}
//---------------------------------------------------------------------------------------------------------------------------
@ -503,12 +503,16 @@ void CoreModel::onMessageReceived(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::shared_ptr<linphone::ChatMessage> &message) {
emit unreadNotificationsChanged();
std::list<std::shared_ptr<linphone::ChatMessage>> messages;
messages.push_back(message);
App::getInstance()->getNotifier()->notifyReceivedMessages(room, messages);
emit messageReceived(core, room, message);
}
void CoreModel::onMessagesReceived(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) {
emit unreadNotificationsChanged();
App::getInstance()->getNotifier()->notifyReceivedMessages(room, messages);
emit messagesReceived(core, room, messages);
}

View file

@ -34,8 +34,8 @@
#include "model/cli/CliModel.hpp"
#include "model/listener/Listener.hpp"
#include "model/logger/LoggerModel.hpp"
#include "tool/AbstractObject.hpp"
#include "model/search/MagicSearchModel.hpp"
#include "tool/AbstractObject.hpp"
// =============================================================================
@ -61,9 +61,9 @@ public:
void migrate();
void searchInMagicSearch(QString filter,
int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults);
int sourceFlags,
LinphoneEnums::MagicSearchAggregation aggregation,
int maxResults);
bool mEnd = false;

View file

@ -120,20 +120,18 @@ std::shared_ptr<linphone::Friend> ToolModel::findFriendByAddress(const QString &
auto defaultFriendList = CoreModel::getInstance()->getCore()->getDefaultFriendList();
if (!defaultFriendList) return nullptr;
auto linphoneAddr = ToolModel::interpretUrl(address);
if (linphoneAddr)
return ToolModel::findFriendByAddress(linphoneAddr);
else
return nullptr;
if (linphoneAddr) return ToolModel::findFriendByAddress(linphoneAddr);
else return nullptr;
}
std::shared_ptr<linphone::Friend> ToolModel::findFriendByAddress(std::shared_ptr<linphone::Address> linphoneAddr) {
auto friendsManager = FriendsManager::getInstance();
QString key = Utils::coreStringToAppString(linphoneAddr->asStringUriOnly());
if (friendsManager->isInKnownFriends(key)) {
// qDebug() << key << "have been found in known friend, return it";
// qDebug() << key << "have been found in known friend, return it";
return friendsManager->getKnownFriendAtKey(key);
} else if (friendsManager->isInUnknownFriends(key)) {
// qDebug() << key << "have been found in unknown friend, return it";
} else if (friendsManager->isInUnknownFriends(key)) {
// qDebug() << key << "have been found in unknown friend, return it";
return friendsManager->getUnknownFriendAtKey(key);
}
auto f = CoreModel::getInstance()->getCore()->findFriend(linphoneAddr);
@ -141,20 +139,21 @@ std::shared_ptr<linphone::Friend> ToolModel::findFriendByAddress(std::shared_ptr
if (friendsManager->isInUnknownFriends(key)) {
friendsManager->removeUnknownFriend(key);
}
// qDebug() << "found friend, add to known map";
// qDebug() << "found friend, add to known map";
friendsManager->appendKnownFriend(linphoneAddr, f);
}
if (!f) {
if (friendsManager->isInOtherAddresses(key)) {
// qDebug() << "A magic search has already be done for address" << key << "and nothing was found, return";
// qDebug() << "A magic search has already be done for address" << key << "and nothing was found,
// return";
return nullptr;
}
friendsManager->appendOtherAddress(key);
// qDebug() << "Couldn't find friend" << linphoneAddr->asStringUriOnly() << "in core, use magic search";
// qDebug() << "Couldn't find friend" << linphoneAddr->asStringUriOnly() << "in core, use magic search";
CoreModel::getInstance()->searchInMagicSearch(Utils::coreStringToAppString(linphoneAddr->asStringUriOnly()),
(int)linphone::MagicSearch::Source::LdapServers
| (int)linphone::MagicSearch::Source::RemoteCardDAV
, LinphoneEnums::MagicSearchAggregation::Friend, 50);
(int)linphone::MagicSearch::Source::LdapServers |
(int)linphone::MagicSearch::Source::RemoteCardDAV,
LinphoneEnums::MagicSearchAggregation::Friend, 50);
}
return f;
}
@ -384,7 +383,6 @@ bool ToolModel::friendIsInFriendList(const std::shared_ptr<linphone::FriendList>
return (it != friends.end());
}
QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) {
for (auto &content : contents) {
if (content->isText()) {
@ -467,3 +465,118 @@ QString ToolModel::computeUserAgent(const std::shared_ptr<linphone::Config> &con
.arg(qVersion())
.remove("'");
}
std::shared_ptr<linphone::ConferenceParams>
ToolModel::getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_ptr<linphone::Address> remoteAddress) {
auto core = call ? call->getCore() : CoreModel::getInstance()->getCore();
auto localAddress = call ? call->getCallLog()->getLocalAddress() : nullptr;
if (!remoteAddress && call) remoteAddress = call->getRemoteAddress()->clone();
auto account = findAccount(localAddress);
if (!account) account = core->getDefaultAccount();
if (!account) qWarning() << "failed to get account, return";
if (!account) return nullptr;
auto params = core->createConferenceParams(call ? call->getConference() : nullptr);
params->enableChat(true);
params->enableGroup(false);
//: Dummy subject
params->setSubject(Utils::appStringToCoreString(QObject::tr("chat_dummy_subject")));
params->setAccount(account);
auto chatParams = params->getChatParams();
if (!chatParams) {
qWarning() << "failed to get chat params from conference params, return";
return nullptr;
}
chatParams->setEphemeralLifetime(0);
auto accountParams = account->getParams();
auto sameDomain = remoteAddress && remoteAddress->getDomain() == SettingsModel::getInstance()->getDefaultDomain() &&
remoteAddress->getDomain() == accountParams->getDomain();
if (accountParams->getInstantMessagingEncryptionMandatory() && sameDomain) {
qDebug() << "Account is in secure mode & domain matches, requesting E2E encryption";
chatParams->setBackend(linphone::ChatRoom::Backend::FlexisipChat);
params->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
} else if (!accountParams->getInstantMessagingEncryptionMandatory()) {
if (SettingsModel::getInstance()->getCreateEndToEndEncryptedMeetingsAndGroupCalls()) {
qDebug() << "Account is in interop mode but LIME is available, requesting E2E encryption";
chatParams->setBackend(linphone::ChatRoom::Backend::FlexisipChat);
params->setSecurityLevel(linphone::Conference::SecurityLevel::EndToEnd);
} else {
qDebug() << "Account is in interop mode and LIME is available, disabling E2E encryption";
chatParams->setBackend(linphone::ChatRoom::Backend::Basic);
params->setSecurityLevel(linphone::Conference::SecurityLevel::None);
}
} else {
qDebug() << "Account is in secure mode, can't chat with SIP address of different domain";
return nullptr;
}
return params;
}
std::shared_ptr<linphone::ChatRoom> ToolModel::lookupCurrentCallChat(std::shared_ptr<CallModel> callModel) {
auto call = callModel->getMonitor();
auto conference = callModel->getConference();
if (conference) {
return conference->getChatRoom();
} else {
auto core = CoreModel::getInstance()->getCore();
auto params = getChatRoomParams(call);
auto remoteaddress = call->getRemoteAddress();
auto localAddress = call->getCallLog()->getLocalAddress();
std::list<std::shared_ptr<linphone::Address>> participants;
participants.push_back(remoteaddress->clone());
qDebug() << "Looking for chat with local address" << localAddress->asStringUriOnly() << "and participant"
<< remoteaddress->asStringUriOnly();
auto existingChat = core->searchChatRoom(params, localAddress, nullptr, participants);
if (existingChat) qDebug() << "Found existing chat";
else qDebug() << "Did not find existing chat";
return existingChat;
}
}
std::shared_ptr<linphone::ChatRoom> ToolModel::createCurrentCallChat(std::shared_ptr<CallModel> callModel) {
auto call = callModel->getMonitor();
auto remoteAddress = call->getRemoteAddress();
std::list<std::shared_ptr<linphone::Address>> participants;
participants.push_back(remoteAddress->clone());
auto core = CoreModel::getInstance()->getCore();
auto params = getChatRoomParams(call);
if (!params) {
qWarning() << "failed to create chatroom params for call with" << remoteAddress->asStringUriOnly();
return nullptr;
}
auto chatRoom = core->createChatRoom(params, participants);
return chatRoom;
}
std::shared_ptr<linphone::ChatRoom> ToolModel::lookupChatForAddress(std::shared_ptr<linphone::Address> remoteAddress) {
auto core = CoreModel::getInstance()->getCore();
auto account = core->getDefaultAccount();
if (!account) return nullptr;
auto localAddress = account->getParams()->getIdentityAddress();
if (!localAddress || !remoteAddress) return nullptr;
auto params = getChatRoomParams(nullptr, remoteAddress);
std::list<std::shared_ptr<linphone::Address>> participants;
participants.push_back(remoteAddress->clone());
qDebug() << "Looking for chat with local address" << localAddress->asStringUriOnly() << "and participant"
<< remoteAddress->asStringUriOnly();
auto existingChat = core->searchChatRoom(params, localAddress, nullptr, participants);
if (existingChat) qDebug() << "Found existing chat";
else qDebug() << "Did not find existing chat";
return existingChat;
}
std::shared_ptr<linphone::ChatRoom> ToolModel::createChatForAddress(std::shared_ptr<linphone::Address> remoteAddress) {
std::list<std::shared_ptr<linphone::Address>> participants;
participants.push_back(remoteAddress->clone());
auto core = CoreModel::getInstance()->getCore();
auto params = getChatRoomParams(nullptr, remoteAddress);
if (!params) {
qWarning() << "failed to create chatroom params for address" << remoteAddress->asStringUriOnly();
return nullptr;
}
auto chatRoom = core->createChatRoom(params, participants);
return chatRoom;
}

View file

@ -81,6 +81,13 @@ public:
static QString getOsProduct();
static QString computeUserAgent(const std::shared_ptr<linphone::Config> &config);
static std::shared_ptr<linphone::ConferenceParams>
getChatRoomParams(std::shared_ptr<linphone::Call> call, std::shared_ptr<linphone::Address> remoteAddress = nullptr);
static std::shared_ptr<linphone::ChatRoom> lookupCurrentCallChat(std::shared_ptr<CallModel> callModel);
static std::shared_ptr<linphone::ChatRoom> createCurrentCallChat(std::shared_ptr<CallModel> callModel);
static std::shared_ptr<linphone::ChatRoom> lookupChatForAddress(std::shared_ptr<linphone::Address> remoteAddress);
static std::shared_ptr<linphone::ChatRoom> createChatForAddress(std::shared_ptr<linphone::Address> remoteAddress);
private:
DECLARE_ABSTRACT_OBJECT
};

View file

@ -22,6 +22,8 @@
#include "core/App.hpp"
#include "core/call/CallGui.hpp"
#include "core/chat/ChatCore.hpp"
#include "core/chat/ChatGui.hpp"
#include "core/conference/ConferenceCore.hpp"
#include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp"
@ -1508,6 +1510,76 @@ Utils::createFriendDeviceVariant(const QString &name, const QString &address, Li
return map;
}
VariantObject *Utils::getCurrentCallChat(CallGui *call) {
VariantObject *data = new VariantObject("lookupCurrentCallChat");
if (!data) return nullptr;
if (!call || !call->mCore) return nullptr;
data->makeRequest([callModel = call->mCore->getModel(), data]() {
if (!callModel) return QVariant();
auto linphoneChatRoom = ToolModel::lookupCurrentCallChat(callModel);
if (linphoneChatRoom) {
auto chatCore = ChatCore::create(linphoneChatRoom);
return QVariant::fromValue(new ChatGui(chatCore));
} else {
qDebug() << "Did not find existing chat room, create one";
linphoneChatRoom = ToolModel::createCurrentCallChat(callModel);
if (linphoneChatRoom != nullptr) {
qDebug() << "Chatroom created with" << callModel->getRemoteAddress()->asStringUriOnly();
auto id = linphoneChatRoom->getIdentifier();
auto params = linphoneChatRoom->getCurrentParams();
auto chatCore = ChatCore::create(linphoneChatRoom);
return QVariant::fromValue(new ChatGui(chatCore));
} else {
qWarning() << "Failed to create 1-1 conversation with"
<< callModel->getRemoteAddress()->asStringUriOnly() << "!";
//: Failed to create 1-1 conversation with %1 !
data->mConnection->invokeToCore([] {
showInformationPopup(tr("information_popup_error_title"),
tr("information_popup_chatroom_creation_error_message"), false,
getCallsWindow());
});
return QVariant();
}
}
});
data->requestValue();
return data;
}
VariantObject *Utils::getChatForAddress(QString address) {
VariantObject *data = new VariantObject("lookupCurrentCallChat");
if (!data) return nullptr;
data->makeRequest([address, data]() {
auto linAddr = ToolModel::interpretUrl(address);
if (!linAddr) return QVariant();
auto linphoneChatRoom = ToolModel::lookupChatForAddress(linAddr);
if (linphoneChatRoom) {
auto chatCore = ChatCore::create(linphoneChatRoom);
return QVariant::fromValue(new ChatGui(chatCore));
} else {
qDebug() << "Did not find existing chat room, create one";
linphoneChatRoom = ToolModel::createChatForAddress(linAddr);
if (linphoneChatRoom != nullptr) {
qDebug() << "Chatroom created with" << linAddr->asStringUriOnly();
auto params = linphoneChatRoom->getCurrentParams();
auto chatCore = ChatCore::create(linphoneChatRoom);
return QVariant::fromValue(new ChatGui(chatCore));
} else {
qWarning() << "Failed to create 1-1 conversation with" << linAddr->asStringUriOnly() << "!";
//: Failed to create 1-1 conversation with %1 !
data->mConnection->invokeToCore([] {
showInformationPopup(tr("information_popup_error_title"),
tr("information_popup_chatroom_creation_error_message"), false,
getCallsWindow());
});
return QVariant();
}
}
});
data->requestValue();
return data;
}
// CLI
void Utils::runCommandLine(const QString command) {

View file

@ -51,6 +51,7 @@ class ConferenceInfoGui;
class ConferenceCore;
class ParticipantDeviceCore;
class DownloadablePayloadTypeCore;
class ChatGui;
class Utils : public QObject, public AbstractObject {
Q_OBJECT
@ -88,8 +89,10 @@ public:
Q_INVOKABLE static QString createAvatar(const QUrl &fileUrl); // Return the avatar path
Q_INVOKABLE static QString formatElapsedTime(int seconds,
bool dotsSeparator = true); // Return the elapsed time formated
Q_INVOKABLE static QString
formatDate(const QDateTime &date, bool includeTime = true, bool includeDateIfToday = true, QString format = ""); // Return the date formated
Q_INVOKABLE static QString formatDate(const QDateTime &date,
bool includeTime = true,
bool includeDateIfToday = true,
QString format = ""); // Return the date formated
Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date);
Q_INVOKABLE static QString formatTime(const QDateTime &date); // Return the time formated
Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode);
@ -140,6 +143,8 @@ public:
Q_INVOKABLE QList<QVariant> append(const QList<QVariant> a, const QList<QVariant> b);
Q_INVOKABLE QString getAddressToDisplay(QVariantList addressList, QString filter, QString defaultAddress);
Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call);
Q_INVOKABLE static VariantObject *getChatForAddress(QString address);
// QDir findDirectoryByName(QString startPath, QString name);
static QString getApplicationProduct();

View file

@ -88,6 +88,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
view/Control/Popup/Loading/LoadingPopup.qml
view/Control/Popup/Notification/Notification.qml
view/Control/Popup/Notification/NotificationReceivedCall.qml
view/Control/Popup/Notification/NotificationReceivedMessage.qml
view/Control/Tool/MovableMouseArea.qml
view/Control/Tool/Helper/utils.js

View file

@ -32,6 +32,7 @@ Control.Button {
property bool shadowEnabled: false
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 bool asynchronous: false
spacing: Math.round(5 * DefaultStyle.dp)
@ -126,7 +127,7 @@ Control.Button {
imageWidth: mainItem.icon.width
imageHeight: mainItem.icon.height
colorizationColor: mainItem.checkable && mainItem.checked
? mainItem.checkedColor || mainItem.pressedColor
? mainItem.checkedImageColor || mainItem.checkedColor || mainItem.pressedColor
: mainItem.pressed
? mainItem.pressedImageColor
: mainItem.hovered

View file

@ -193,7 +193,12 @@ ColumnLayout {
button.icon.source: AppIcons.chatTeardropText
//: "Message"
label: qsTr("contact_message_action")
button.onClicked: console.debug("[ContactLayout.qml] TODO : open conversation")
button.onClicked: {
console.debug("[CallHistoryLayout.qml] Open conversation")
if (mainItem.specificAddress === "") {
mainWindow.displayChatPage(mainItem.contact.core.defaultAddress)
} else mainWindow.displayChatPage(mainItem.specificAddress)
}
}
LabelButton {
visible: !mainItem.isConference && SettingsCpp.videoEnabled

View file

@ -10,6 +10,7 @@ ColumnLayout {
property color panelColor: DefaultStyle.grey_100
property alias headerContent: rightPanelHeader.children
property alias content: rightPanelContent.children
property alias header: rightPanelHeader
spacing: 0
Rectangle {

View file

@ -42,6 +42,11 @@ ListView {
// flickDeceleration: 10000
spacing: Math.round(10 * DefaultStyle.dp)
function selectChat(chatGui) {
var index = chatProxy.findChatIndex(chatGui)
mainItem.currentIndex = index
}
Component.onCompleted: cacheBuffer = Math.max(contentHeight, 0) //contentHeight>0 ? contentHeight : 0// cache all items
// remove binding loop
onContentHeightChanged: Qt.callLater(function () {
@ -59,12 +64,13 @@ ListView {
positionViewAtBeginning() // Stay at beginning
}
onAtYEndChanged: {
if (atYEnd && count > 0) {
chatProxy.displayMore()
}
}
//----------------------------------------------------------------
onAtYEndChanged: {
if (atYEnd && count > 0) {
chatProxy.displayMore()
}
}
//----------------------------------------------------------------
function moveToCurrentItem() {
if (mainItem.currentIndex >= 0)
Utils.updatePosition(mainItem, mainItem)
@ -109,8 +115,8 @@ ListView {
component UnreadNotification: Item {
id: unreadNotif
property int unread: 0
width: Math.round(22 * DefaultStyle.dp)
height: Math.round(22 * DefaultStyle.dp)
width: Math.round(14 * DefaultStyle.dp)
height: Math.round(14 * DefaultStyle.dp)
visible: unread > 0
Rectangle {
id: background
@ -123,7 +129,7 @@ ListView {
horizontalAlignment: Text.AlignHCenter
color: DefaultStyle.grey_0
fontSizeMode: Text.Fit
font.pixelSize: Typography.p3.pixelSize
font.pixelSize: Math.round(10 * DefaultStyle.dp)
text: parent.unreadNotif > 100 ? '99+' : unreadNotif.unread
}
}
@ -155,11 +161,7 @@ ListView {
id: historyAvatar
property var contactObj: UtilsCpp.findFriendByAddress(modelData.core.peerAddress)
contact: contactObj?.value || null
onContactChanged: {
if (contact) console.log("found contact", contact.core.defaultAddress)
else console.log("no contact for peer address", modelData.core.peerAddress, modelData.core.avatarUri)
}
displayNameVal: contact ? undefined : modelData.core.avatarUri
displayNameVal: contact ? "" : modelData.core.avatarUri
// secured: securityLevel === LinphoneEnums.SecurityLevel.EndToEndEncryptedAndVerified
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
@ -208,6 +210,7 @@ ListView {
}
RowLayout {
Item {Layout.fillWidth: true}
UnreadNotification {
id: unreadCount
unread: modelData.core.unreadMessagesCount
@ -230,10 +233,8 @@ ListView {
anchors.fill: parent
opacity: 0.7
radius: Math.round(8 * DefaultStyle.dp)
color: mainItem.currentIndex
=== index ? DefaultStyle.main2_200 : DefaultStyle.main2_100
visible: mainItem.lastMouseContainsIndex === index
|| mainItem.currentIndex === index
color: mainItem.currentIndex === index ? DefaultStyle.main2_200 : DefaultStyle.main2_100
visible: mainItem.lastMouseContainsIndex === index || mainItem.currentIndex === index
}
onPressed: {
mainItem.currentIndex = model.index

View file

@ -19,6 +19,10 @@ ListView {
chatGui: mainItem.chat
}
header: Item {
height: Math.round(18 * DefaultStyle.dp)
}
delegate: ChatMessage {
id: chatMessage
width: Math.min(implicitWidth, Math.round(mainItem.width * (3/4)))

View file

@ -170,6 +170,10 @@ FocusScope {
style: ButtonStyle.grey
KeyNavigation.left: videoCallButton
KeyNavigation.right: callButton
onClicked: {
console.debug("[ContactListItem.qml] Open conversation")
mainWindow.displayChatPage(mainItem.addressFromFilter)
}
}
}
PopupButton {

View file

@ -7,7 +7,7 @@ import Qt.labs.platform
// =============================================================================
Window {
Window {
id: mainItem
// ---------------------------------------------------------------------------

View file

@ -0,0 +1,118 @@
import QtQuick
import QtQuick.Layouts
import Linphone
import UtilsCpp
import QtQuick.Controls as Control
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
// =============================================================================
Notification {
id: mainItem
radius: Math.round(20 * DefaultStyle.dp)
backgroundColor: DefaultStyle.grey_600
backgroundOpacity: 0.8
overriddenWidth: Math.round(400 * DefaultStyle.dp)
overriddenHeight: content.height
property string avatarUri: notificationData && notificationData.avatarUri
property string chatRoomName: notificationData && notificationData.chatRoomName
property string remoteAddress: notificationData && notificationData.remoteAddress
property string message: notificationData && notificationData.message
Popup {
id: content
visible: mainItem.visible
width: parent.width
leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp)
topPadding: Math.round(9 * DefaultStyle.dp)
bottomPadding: Math.round(18 * DefaultStyle.dp)
background: Item{}
contentItem: ColumnLayout {
spacing: Math.round(9 * DefaultStyle.dp)
RowLayout {
spacing: Math.round(4 * DefaultStyle.dp)
Layout.alignment: Qt.AlignHCenter
Image {
Layout.preferredWidth: Math.round(12 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(12 * DefaultStyle.dp)
source: AppIcons.logo
}
Text {
text: "Linphone"
color: DefaultStyle.grey_0
font {
pixelSize: Math.round(12 * DefaultStyle.dp)
weight: Typography.b3.weight
capitalization: Font.Capitalize
}
}
}
ColumnLayout {
spacing: Math.round(14 * DefaultStyle.dp)
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
RowLayout {
spacing: Math.round(10 * DefaultStyle.dp)
Avatar {
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
Layout.alignment: Qt.AlignHCenter
property var contactObj: UtilsCpp.findFriendByAddress(mainItem.remoteAddress)
contact: contactObj?.value || null
displayNameVal: contact ? "" : mainItem.avatarUri
}
ColumnLayout {
spacing: 0
Text {
text: mainItem.chatRoomName
color: DefaultStyle.main2_200
Layout.fillWidth: true
maximumLineCount: 1
font {
pixelSize: Typography.h3.pixelSize
weight: Typography.h3.weight
capitalization: Font.Capitalize
}
}
Text {
text: mainItem.remoteAddress
color: DefaultStyle.main2_100
Layout.fillWidth: true
maximumLineCount: 1
font {
pixelSize: Typography.p1.pixelSize
weight: Typography.p1.weight
}
}
}
Item{Layout.fillWidth: true}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Math.round(60 * DefaultStyle.dp)
color: DefaultStyle.main2_400
radius: Math.round(5 * DefaultStyle.dp)
Text {
anchors.fill: parent
anchors.leftMargin: 8 * DefaultStyle.dp
anchors.rightMargin: 8 * DefaultStyle.dp
anchors.topMargin: 8 * DefaultStyle.dp
anchors.bottomMargin: 8 * DefaultStyle.dp
verticalAlignment: Text.AlignVCenter
text: mainItem.message
maximumLineCount: 2
color: DefaultStyle.grey_1000
font {
pixelSize: Typography.p1s.pixelSize
weight: Typography.p1s.weight
italic: true
}
}
}
}
}
}
}

View file

@ -12,21 +12,26 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
RowLayout {
id: mainItem
property ChatGui chat
property CallGui call
property alias callHeaderContent: splitPanel.headerContent
spacing: 0
MainRightPanel {
id: splitPanel
Layout.fillWidth: true
Layout.fillHeight: true
panelColor: DefaultStyle.grey_0
header.visible: !mainItem.call
clip: true
headerContent: [
RowLayout {
anchors.left: parent.left
anchors.leftMargin: Math.round(31 * DefaultStyle.dp)
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent?.left
anchors.leftMargin: mainItem.call ? 0 : Math.round(31 * DefaultStyle.dp)
anchors.verticalCenter: parent?.verticalCenter
spacing: Math.round(12 * DefaultStyle.dp)
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
displayNameVal: contact ? "" : mainItem.chat.core.avatarUri
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
}
@ -38,6 +43,7 @@ RowLayout {
font {
pixelSize: Typography.h4.pixelSize
weight: Math.round(400 * DefaultStyle.dp)
capitalization: Font.Capitalize
}
}
},
@ -51,7 +57,7 @@ RowLayout {
}
BigButton {
style: ButtonStyle.noBackground
icon.source: AppIcons.camera
icon.source: AppIcons.videoCamera
}
BigButton {
style: ButtonStyle.noBackground

View file

@ -26,6 +26,7 @@ Item {
signal openCallHistory
signal openNumPadRequest
signal displayContactRequested(string contactAddress)
signal displayChatRequested(string contactAddress)
signal createContactRequested(string name, string address)
signal accountRemoved
@ -41,6 +42,10 @@ Item {
tabbar.currentIndex = 1
mainItem.displayContactRequested(contactAddress)
}
function displayChatPage(contactAddress) {
tabbar.currentIndex = 2
mainItem.displayChatRequested(contactAddress)
}
function createContact(name, address) {
tabbar.currentIndex = 1
@ -616,7 +621,17 @@ Item {
}
}
}
ChatPage{}
ChatPage {
id: chatPage
Connections {
target: mainItem
function onDisplayChatRequested(contactAddress) {
console.log("display chat requested, open with address", contactAddress)
chatPage.remoteAddress = ""
chatPage.remoteAddress = contactAddress
}
}
}
MeetingPage {}
}
}

View file

@ -532,12 +532,9 @@ AbstractMainPage {
onClicked: {
detailOptions.close()
if (contactDetail.isLocalFriend)
mainWindow.displayContactPage(
contactDetail.contactAddress)
mainWindow.displayContactPage(contactDetail.contactAddress)
else
mainItem.createContactRequested(
contactDetail.contactName,
contactDetail.contactAddress)
mainItem.createContactRequested(contactDetail.contactName, contactDetail.contactAddress)
}
}
IconLabelButton {

View file

@ -32,7 +32,7 @@ Control.Page {
header: Control.Control {
id: pageHeader
width: mainItem.width
height: Math.round(56 * DefaultStyle.dp)
height: Math.round(67 * DefaultStyle.dp)
leftPadding: Math.round(10 * DefaultStyle.dp)
rightPadding: Math.round(10 * DefaultStyle.dp)
background: Rectangle {

View file

@ -18,6 +18,12 @@ AbstractMainPage {
property var selectedChatGui
property string remoteAddress
onRemoteAddressChanged: console.log("ChatPage : remote address changed :", remoteAddress)
property var remoteChatObj: UtilsCpp.getChatForAddress(remoteAddress)
property ChatGui remoteChat: remoteChatObj ? remoteChatObj.value : null
onRemoteChatChanged: if (remoteChat) selectedChatGui = remoteChat
onSelectedChatGuiChanged: {
if (selectedChatGui)
rightPanelStackView.replace(currentChatComp,
@ -151,6 +157,11 @@ AbstractMainPage {
onCountChanged: {
mainItem.selectedChatGui = model.getAt(currentIndex)
}
Connections {
target: mainItem
onSelectedChatGuiChanged: chatListView.selectChat(mainItem.selectedChatGui)
}
}
}
ScrollBar {

View file

@ -411,9 +411,7 @@ FriendGui{
height: Math.round(56 * DefaultStyle.dp)
button.icon.width: Math.round(24 * DefaultStyle.dp)
button.icon.height: Math.round(24 * DefaultStyle.dp)
button.onClicked: mainWindow.startCallWithContact(
contactDetail.contact,
false, mainItem)
button.onClicked: mainWindow.startCallWithContact(contactDetail.contact, false, mainItem)
}
LabelButton {
button.icon.source: AppIcons.chatTeardropText
@ -424,8 +422,10 @@ FriendGui{
height: Math.round(56 * DefaultStyle.dp)
button.icon.width: Math.round(24 * DefaultStyle.dp)
button.icon.height: Math.round(24 * DefaultStyle.dp)
button.onClicked: console.debug(
"[ContactLayout.qml] TODO : open conversation")
button.onClicked: {
console.debug("[ContactLayout.qml] Open conversation")
mainWindow.displayChatPage(contactDetail.contact.core.defaultAddress)
}
}
LabelButton {
visible: SettingsCpp.videoEnabled
@ -436,9 +436,7 @@ FriendGui{
height: Math.round(56 * DefaultStyle.dp)
button.icon.width: Math.round(24 * DefaultStyle.dp)
button.icon.height: Math.round(24 * DefaultStyle.dp)
button.onClicked: mainWindow.startCallWithContact(
contactDetail.contact,
true, mainItem)
button.onClicked: mainWindow.startCallWithContact(contactDetail.contact, true, mainItem)
}
}
bannerContent: [
@ -636,7 +634,7 @@ FriendGui{
EffectImage {
Layout.preferredWidth: Math.round(24 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(24 * DefaultStyle.dp)
source: AppIcons.shareNetwork
imageSource: AppIcons.shareNetwork
colorizationColor: DefaultStyle.main2_600
}
Text {

View file

@ -620,6 +620,19 @@ AbstractWindow {
contentStackView.initialItem: callListPanel
headerValidateButtonText: qsTr("add")
Binding on topPadding {
when: rightPanel.contentStackView.currentItem.objectName === "chatPanel"
value: 0
}
Binding on leftPadding {
when: rightPanel.contentStackView.currentItem.objectName === "chatPanel"
value: 0
}
Binding on rightPadding {
when: rightPanel.contentStackView.currentItem.objectName == "chatPanel"
value: 0
}
Item {
id: numericPadContainer
anchors.bottom: parent.bottom
@ -836,6 +849,28 @@ AbstractWindow {
}
}
}
Component {
id: chatPanel
Item {
id: chatPanelContent
objectName: "chatPanel"
Control.StackView.onActivated: {
rightPanel.customHeaderButtons = chatView.callHeaderContent
rightPanel.headerTitleText = ""
}
Keys.onEscapePressed: event => {
rightPanel.visible = false
event.accepted = true
}
SelectedChatView {
id: chatView
anchors.fill: parent
call: mainWindow.call
property var chatObj: UtilsCpp.getCurrentCallChat(mainWindow.call)
chat: chatObj ? chatObj.value : null
}
}
}
Component {
id: settingsPanel
Item {
@ -1313,6 +1348,23 @@ AbstractWindow {
}
}
}
CheckableButton {
iconUrl: AppIcons.chatTeardropText
//: Open chat
ToolTip.text: qsTr("call_open_chat_hint")
Layout.preferredWidth: Math.round(55 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(55 * DefaultStyle.dp)
icon.width: Math.round(32 * DefaultStyle.dp)
icon.height: Math.round(32 * DefaultStyle.dp)
onCheckedChanged: {
if (checked) {
rightPanel.visible = true
rightPanel.replace(chatPanel)
} else {
rightPanel.visible = false
}
}
}
CheckableButton {
visible: false
checkable: false

View file

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

View file

@ -121,7 +121,8 @@
},
image: {
normal: Linphone.DefaultStyle.grey_0,
pressed: Linphone.DefaultStyle.grey_0
pressed: Linphone.DefaultStyle.grey_0,
checked: Linphone.DefaultStyle.grey_0
}
}