imdn on each chat message

imdn on chat last message

change message notification ui

press enter to send message
This commit is contained in:
Gaelle Braud 2025-05-12 16:01:00 +02:00
parent c0dbc4b0e5
commit 9b67794752
22 changed files with 427 additions and 248 deletions

View file

@ -69,18 +69,22 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
});
mChatModel = Utils::makeQObject_ptr<ChatModel>(chatRoom);
mChatModel->setSelf(mChatModel);
mLastMessageInHistory = mChatModel->getLastMessageInHistory();
auto lastMessage = chatRoom->getLastMessageInHistory();
mLastMessage = lastMessage ? ChatMessageCore::create(lastMessage) : nullptr;
auto history = chatRoom->getHistory(0, (int)linphone::ChatRoom::HistoryFilter::ChatMessage);
std::list<std::shared_ptr<linphone::ChatMessage>> linHistory;
std::list<std::shared_ptr<linphone::ChatMessage>> lHistory;
for (auto &eventLog : history) {
if (eventLog->getChatMessage()) linHistory.push_back(eventLog->getChatMessage());
if (eventLog->getChatMessage()) lHistory.push_back(eventLog->getChatMessage());
}
for (auto &message : linHistory) {
for (auto &message : lHistory) {
if (!message) continue;
auto chatMessage = ChatMessageCore::create(message);
mChatMessageList.append(chatMessage);
}
mIdentifier = Utils::coreStringToAppString(chatRoom->getIdentifier());
connect(this, &ChatCore::messageListChanged, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messagesInserted, this, &ChatCore::lUpdateLastMessage);
connect(this, &ChatCore::messageRemoved, this, &ChatCore::lUpdateLastMessage);
}
ChatCore::~ChatCore() {
@ -123,16 +127,14 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {
if (mChatModel->getMonitor() != chatRoom) return;
emit lUpdateLastMessage();
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
auto message = eventLog->getChatMessage();
qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
if (message) {
auto newMessage = ChatMessageCore::create(message);
mChatModelConnection->invokeToCore([this, newMessage]() {
qDebug() << log().arg("append message to chatRoom") << this;
appendMessageToMessageList(newMessage);
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
});
}
});
@ -140,9 +142,6 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::list<std::shared_ptr<linphone::EventLog>> &chatMessages) {
if (mChatModel->getMonitor() != chatRoom) return;
emit lUpdateLastMessage();
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
qDebug() << "EVENT LOGS RECEIVED IN CHATROOM" << mChatModel->getTitle();
QList<QSharedPointer<ChatMessageCore>> list;
for (auto &m : chatMessages) {
@ -153,8 +152,9 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
}
}
mChatModelConnection->invokeToCore([this, list]() {
qDebug() << log().arg("append messages to chatRoom") << this;
appendMessagesToMessageList(list);
emit lUpdateUnreadCount();
emit lUpdateLastUpdatedTime();
});
});
@ -167,12 +167,17 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
});
mChatModelConnection->makeConnectToCore(&ChatCore::lUpdateLastMessage, [this]() {
mChatModelConnection->invokeToModel([this]() {
auto message = mChatModel->getLastMessageInHistory();
mChatModelConnection->invokeToCore([this, message]() { setLastMessageInHistory(message); });
auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr;
mChatModelConnection->invokeToModel([this, lastMessageModel]() {
auto linphoneMessage = mChatModel->getLastChatMessage();
if (lastMessageModel && lastMessageModel->getMonitor() != linphoneMessage) {
auto chatMessageCore = ChatMessageCore::create(linphoneMessage);
mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); });
}
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSendTextMessage, [this](QString message) {
if (Utils::isEmptyMessage(message)) return;
mChatModelConnection->invokeToModel([this, message]() {
auto linMessage = mChatModel->createTextMessageFromText(message);
linMessage->send();
@ -253,14 +258,28 @@ void ChatCore::setAvatarUri(QString avatarUri) {
}
}
QString ChatCore::getLastMessageInHistory() const {
return mLastMessageInHistory;
QString ChatCore::getLastMessageText() const {
return mLastMessage ? mLastMessage->getText() : QString();
}
void ChatCore::setLastMessageInHistory(QString lastMessageInHistory) {
if (mLastMessageInHistory != lastMessageInHistory) {
mLastMessageInHistory = lastMessageInHistory;
emit lastMessageInHistoryChanged(lastMessageInHistory);
LinphoneEnums::ChatMessageState ChatCore::getLastMessageState() const {
return mLastMessage ? mLastMessage->getMessageState() : LinphoneEnums::ChatMessageState::StateIdle;
}
ChatMessageGui *ChatCore::getLastMessage() const {
return mLastMessage ? new ChatMessageGui(mLastMessage) : nullptr;
}
QSharedPointer<ChatMessageCore> ChatCore::getLastMessageCore() const {
return mLastMessage;
}
void ChatCore::setLastMessage(QSharedPointer<ChatMessageCore> lastMessage) {
if (mLastMessage != lastMessage) {
disconnect(mLastMessage.get());
mLastMessage = lastMessage;
connect(mLastMessage.get(), &ChatMessageCore::messageStateChanged, this, &ChatCore::lastMessageChanged);
emit lastMessageChanged();
}
}

View file

@ -21,7 +21,7 @@
#ifndef CHAT_CORE_H_
#define CHAT_CORE_H_
#include "core/chat/message/ChatMessageCore.hpp"
#include "core/chat/message/ChatMessageGui.hpp"
#include "model/chat/ChatModel.hpp"
#include "model/search/MagicSearchModel.hpp"
#include "tool/LinphoneEnums.hpp"
@ -39,8 +39,9 @@ public:
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)
Q_PROPERTY(QString lastMessageInHistory READ getLastMessageInHistory WRITE setLastMessageInHistory NOTIFY
lastMessageInHistoryChanged)
Q_PROPERTY(QString lastMessageText READ getLastMessageText NOTIFY lastMessageChanged)
Q_PROPERTY(ChatMessageGui *lastMessage READ getLastMessage NOTIFY lastMessageChanged)
Q_PROPERTY(LinphoneEnums::ChatMessageState lastMessageState READ getLastMessageState NOTIFY lastMessageChanged)
Q_PROPERTY(int unreadMessagesCount READ getUnreadMessagesCount WRITE setUnreadMessagesCount NOTIFY
unreadMessagesCountChanged)
Q_PROPERTY(QString composingName READ getComposingName WRITE setComposingName NOTIFY composingUserChanged)
@ -61,8 +62,13 @@ public:
QString getIdentifier() const;
QString getLastMessageInHistory() const;
void setLastMessageInHistory(QString message);
ChatMessageGui *getLastMessage() const;
QString getLastMessageText() const;
LinphoneEnums::ChatMessageState getLastMessageState() const;
QSharedPointer<ChatMessageCore> getLastMessageCore() const;
void setLastMessage(QSharedPointer<ChatMessageCore> lastMessage);
int getUnreadMessagesCount() const;
void setUnreadMessagesCount(int count);
@ -89,7 +95,7 @@ public:
signals:
void lastUpdatedTimeChanged(QDateTime time);
void lastMessageInHistoryChanged(QString time);
void lastMessageChanged();
void titleChanged(QString title);
void peerAddressChanged(QString address);
void unreadMessagesCountChanged(int count);
@ -113,7 +119,6 @@ signals:
private:
QString id;
QDateTime mLastUpdatedTime;
QString mLastMessageInHistory;
QString mPeerAddress;
QString mTitle;
QString mIdentifier;
@ -122,6 +127,7 @@ private:
QString mComposingName;
QString mComposingAddress;
std::shared_ptr<ChatModel> mChatModel;
QSharedPointer<ChatMessageCore> mLastMessage;
QList<QSharedPointer<ChatMessageCore>> mChatMessageList;
QSharedPointer<SafeConnection<ChatCore, ChatModel>> mChatModelConnection;

View file

@ -52,6 +52,25 @@ ChatList::~ChatList() {
mModelConnection = nullptr;
}
void ChatList::connectItem(QSharedPointer<ChatCore> chat) {
connect(chat.get(), &ChatCore::deleted, this, [this, chat] {
remove(chat);
// We cannot use countChanged here because it is called before mList
// really has removed the item, then emit specific signal
emit chatRemoved(chat ? new ChatGui(chat) : nullptr);
});
auto dataChange = [this, chat] {
int i = -1;
get(chat.get(), &i);
if (i != -1) {
auto modelIndex = index(i);
emit dataChanged(modelIndex, modelIndex);
}
};
connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, dataChange);
connect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, dataChange);
}
void ChatList::setSelf(QSharedPointer<ChatList> me) {
mModelConnection = SafeConnection<ChatList, CoreModel>::create(me, CoreModel::getInstance());
@ -70,16 +89,12 @@ void ChatList::setSelf(QSharedPointer<ChatList> me) {
for (auto &chat : getSharedList<ChatCore>()) {
if (chat) {
disconnect(chat.get(), &ChatCore::deleted, this, nullptr);
disconnect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, nullptr);
disconnect(chat.get(), &ChatCore::lastUpdatedTimeChanged, this, nullptr);
}
}
for (auto &chat : *chats) {
connect(chat.get(), &ChatCore::deleted, this, [this, chat] {
remove(chat);
// We cannot use countChanged here because it is called before mList
// really has removed the item, then emit specific signal
emit chatRemoved(chat ? new ChatGui(chat) : nullptr);
});
connect(chat.get(), &ChatCore::unreadMessagesCountChanged, this, [this] { emit chatUpdated(); });
connectItem(chat);
}
mustBeInMainThread(getClassName());
resetData<ChatCore>(*chats);

View file

@ -39,6 +39,7 @@ public:
ChatList(QObject *parent = Q_NULLPTR);
~ChatList();
void setSelf(QSharedPointer<ChatList> me);
void connectItem(QSharedPointer<ChatCore> chat);
int findChatIndex(ChatGui *chat);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

View file

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

View file

@ -54,6 +54,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) &&
!chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::OneToOne);
mIsRead = chatmessage->isRead();
mMessageState = LinphoneEnums::fromLinphone(chatmessage->getState());
}
ChatMessageCore::~ChatMessageCore() {
@ -72,6 +73,14 @@ void ChatMessageCore::setSelf(QSharedPointer<ChatMessageCore> me) {
mChatMessageModelConnection->invokeToModel([this] { mChatMessageModel->markAsRead(); });
});
mChatMessageModelConnection->makeConnectToModel(&ChatMessageModel::messageRead, [this]() { setIsRead(true); });
mChatMessageModelConnection->makeConnectToModel(
&ChatMessageModel::msgStateChanged,
[this](const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state) {
if (mChatMessageModel->getMonitor() != message) return;
auto msgState = LinphoneEnums::fromLinphone(state);
mChatMessageModelConnection->invokeToCore([this, msgState] { setMessageState(msgState); });
});
}
QDateTime ChatMessageCore::getTimestamp() const {
@ -135,6 +144,17 @@ void ChatMessageCore::setIsRead(bool read) {
}
}
LinphoneEnums::ChatMessageState ChatMessageCore::getMessageState() const {
return mMessageState;
}
void ChatMessageCore::setMessageState(LinphoneEnums::ChatMessageState state) {
if (mMessageState != state) {
mMessageState = state;
emit messageStateChanged();
}
}
std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
return mChatMessageModel;
}

View file

@ -40,6 +40,8 @@ class ChatMessageCore : public QObject, public AbstractObject {
Q_PROPERTY(QString toAddress READ getToAddress CONSTANT)
Q_PROPERTY(QString peerName READ getPeerName CONSTANT)
Q_PROPERTY(QString fromName READ getFromName CONSTANT)
Q_PROPERTY(LinphoneEnums::ChatMessageState messageState READ getMessageState WRITE setMessageState NOTIFY
messageStateChanged)
Q_PROPERTY(bool isRemoteMessage READ isRemoteMessage CONSTANT)
Q_PROPERTY(bool isFromChatGroup READ isFromChatGroup CONSTANT)
Q_PROPERTY(bool isRead READ isRead WRITE setIsRead NOTIFY isReadChanged)
@ -68,6 +70,9 @@ public:
bool isRead() const;
void setIsRead(bool read);
LinphoneEnums::ChatMessageState getMessageState() const;
void setMessageState(LinphoneEnums::ChatMessageState state);
std::shared_ptr<ChatMessageModel> getModel() const;
signals:
@ -75,6 +80,7 @@ signals:
void textChanged(QString text);
void isReadChanged(bool read);
void isRemoteMessageChanged(bool isRemote);
void messageStateChanged();
void lDelete();
void deleted();
@ -92,6 +98,8 @@ private:
bool mIsRemoteMessage = false;
bool mIsFromChatGroup = false;
bool mIsRead = false;
LinphoneEnums::ChatMessageState mMessageState;
std::shared_ptr<ChatMessageModel> mChatMessageModel;
QSharedPointer<SafeConnection<ChatMessageCore, ChatMessageModel>> mChatMessageModelConnection;
};

View file

@ -88,23 +88,8 @@ QString ChatModel::getPeerAddress() const {
return Utils::coreStringToAppString(mMonitor->getPeerAddress()->asStringUriOnly());
}
QString ChatModel::getLastMessageInHistory(std::list<std::shared_ptr<linphone::Content>> startList) const {
if (startList.empty()) {
auto lastMessage = mMonitor->getLastMessageInHistory();
if (lastMessage) startList = lastMessage->getContents();
}
for (auto &content : startList) {
if (content->isText()) {
return Utils::coreStringToAppString(content->getUtf8Text());
} else if (content->isFile()) {
return Utils::coreStringToAppString(content->getName());
} else if (content->isIcalendar()) {
return QString("Invitation à une réunion");
} else if (content->isMultipart()) {
return getLastMessageInHistory(content->getParts());
}
}
return QString("");
std::shared_ptr<linphone::ChatMessage> ChatModel::getLastChatMessage() {
return mMonitor->getLastMessageInHistory();
}
int ChatModel::getUnreadMessagesCount() const {
@ -137,6 +122,10 @@ void ChatModel::compose() {
mMonitor->compose();
}
linphone::ChatRoom::State ChatModel::getState() const {
return mMonitor->getState();
}
//---------------------------------------------------------------//
void ChatModel::onIsComposingReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,

View file

@ -40,7 +40,7 @@ public:
QDateTime getLastUpdateTime();
QString getTitle();
QString getPeerAddress() const;
QString getLastMessageInHistory(std::list<std::shared_ptr<linphone::Content>> startList = {}) const;
std::shared_ptr<linphone::ChatMessage> getLastChatMessage();
int getUnreadMessagesCount() const;
void markAsRead();
std::list<std::shared_ptr<linphone::ChatMessage>> getHistory() const;
@ -49,6 +49,7 @@ public:
void deleteChatRoom();
std::shared_ptr<linphone::ChatMessage> createTextMessageFromText(QString text);
void compose();
linphone::ChatRoom::State getState() const;
signals:
void historyDeleted();

View file

@ -60,14 +60,6 @@ QDateTime ChatMessageModel::getTimestamp() const {
return QDateTime::fromSecsSinceEpoch(mMonitor->getTime());
}
std::shared_ptr<linphone::Buffer>
ChatMessageModel::onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size) {
return nullptr;
}
bool ChatMessageModel::isRead() const {
return mMonitor->isRead();
}
@ -85,3 +77,88 @@ void ChatMessageModel::deleteMessageFromChatRoom() {
emit messageDeleted();
}
}
linphone::ChatMessage::State ChatMessageModel::getState() const {
return mMonitor->getState();
}
void ChatMessageModel::computeDeliveryStatus() {
// Read
for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::Displayed)) {
}
// Received
for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::DeliveredToUser)) {
}
// Sent
for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::Delivered)) {
}
// Error
for (auto &participant : mMonitor->getParticipantsByImdnState(linphone::ChatMessage::State::NotDelivered)) {
}
}
void ChatMessageModel::onMsgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
linphone::ChatMessage::State state) {
computeDeliveryStatus();
emit msgStateChanged(message, state);
}
void ChatMessageModel::onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction) {
emit newMessageReaction(message, reaction);
}
void ChatMessageModel::onReactionRemoved(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::Address> &address) {
emit reactionRemoved(message, address);
}
void ChatMessageModel::onFileTransferTerminated(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content) {
emit fileTransferTerminated(message, content);
}
void ChatMessageModel::onFileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
const std::shared_ptr<const linphone::Buffer> &buffer) {
emit fileTransferRecv(message, content, buffer);
}
std::shared_ptr<linphone::Buffer>
ChatMessageModel::onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size) {
emit fileTransferSend(message, content, offset, size);
return nullptr;
}
void ChatMessageModel::onFileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size,
const std::shared_ptr<linphone::Buffer> &buffer) {
emit fileTransferSendChunk(message, content, offset, size, buffer);
}
void ChatMessageModel::onFileTransferProgressIndication(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t total) {
emit fileTransferProgressIndication(message, content, offset, total);
}
void ChatMessageModel::onParticipantImdnStateChanged(
const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ParticipantImdnState> &state) {
computeDeliveryStatus();
emit participantImdnStateChanged(message, state);
}
void ChatMessageModel::onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message) {
emit ephemeralMessageTimerStarted(message);
}
void ChatMessageModel::onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message) {
emit ephemeralMessageDeleted(message);
}

View file

@ -49,16 +49,74 @@ public:
void deleteMessageFromChatRoom();
void computeDeliveryStatus();
linphone::ChatMessage::State getState() const;
signals:
void messageDeleted();
void messageRead();
void msgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state);
void newMessageReaction(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction);
void reactionRemoved(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::Address> &address);
void fileTransferTerminated(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content);
void fileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
const std::shared_ptr<const linphone::Buffer> &buffer);
void fileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size);
void fileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size,
const std::shared_ptr<linphone::Buffer> &buffer);
void fileTransferProgressIndication(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t total);
void participantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ParticipantImdnState> &state);
void ephemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message);
void ephemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message);
private:
linphone::ChatMessage::State mMessageState;
DECLARE_ABSTRACT_OBJECT
virtual std::shared_ptr<linphone::Buffer> onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size) override;
void onMsgStateChanged(const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state);
void onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ChatMessageReaction> &reaction);
void onReactionRemoved(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::Address> &address);
void onFileTransferTerminated(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content);
void onFileTransferRecv(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
const std::shared_ptr<const linphone::Buffer> &buffer);
std::shared_ptr<linphone::Buffer> onFileTransferSend(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size);
void onFileTransferSendChunk(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t size,
const std::shared_ptr<linphone::Buffer> &buffer);
void onFileTransferProgressIndication(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<linphone::Content> &content,
size_t offset,
size_t total);
void onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> &message,
const std::shared_ptr<const linphone::ParticipantImdnState> &state);
void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> &message);
void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> &message);
};
#endif

View file

@ -1582,6 +1582,10 @@ VariantObject *Utils::getChatForAddress(QString address) {
return data;
}
bool Utils::isEmptyMessage(QString message) {
return message.trimmed().isEmpty();
}
// CLI
void Utils::runCommandLine(const QString command) {

View file

@ -148,6 +148,7 @@ public:
Q_INVOKABLE static VariantObject *getCurrentCallChat(CallGui *call);
Q_INVOKABLE static VariantObject *getChatForAddress(QString address);
Q_INVOKABLE static bool isEmptyMessage(QString message);
// QDir findDirectoryByName(QString startPath, QString name);
static QString getApplicationProduct();

View file

@ -185,7 +185,7 @@ ListView {
Text {
Layout.fillWidth: true
maximumLineCount: 1
text: modelData.core.lastMessageInHistory
text: modelData.core.lastMessageText
color: DefaultStyle.main2_400
font {
pixelSize: Typography.p1.pixelSize
@ -194,25 +194,45 @@ ListView {
}
}
ColumnLayout {
Text {
color: DefaultStyle.main2_500main
text: UtilsCpp.formatDate(modelData.core.lastUpdatedTime, true, false)
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
capitalization: Font.Capitalize
Layout.alignment: Qt.AlignRight
RowLayout {
Item{Layout.fillWidth: true}
Text {
color: DefaultStyle.main2_500main
text: UtilsCpp.formatDate(modelData.core.lastUpdatedTime, true, false)
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
capitalization: Font.Capitalize
}
}
}
RowLayout {
spacing: Math.round(10 * DefaultStyle.dp)
Item {Layout.fillWidth: true}
//sourdine, éphémère
UnreadNotification {
id: unreadCount
unread: modelData.core.unreadMessagesCount
}
//sourdine, éphémère, IMDN
EffectImage {
visible: modelData?.core.lastMessage && modelData?.core.lastMessageState !== LinphoneEnums.ChatMessageState.StateIdle
&& !modelData.core.lastMessage.core.isRemoteMessage
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp
colorizationColor: DefaultStyle.main1_500_main
imageSource: modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDelivered
? AppIcons.envelope
: modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
? AppIcons.check
: modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateNotDelivered
? AppIcons.warningCircle
: modelData.core.lastMessageState === LinphoneEnums.ChatMessageState.StateDisplayed
? AppIcons.checks
: ""
}
}
}
PopupButton {
@ -251,6 +271,7 @@ ListView {
hoverEnabled: true
anchors.fill: parent
focus: true
acceptedButtons: Qt.RightButton | Qt.LeftButton
onContainsMouseChanged: {
if (containsMouse)
mainItem.lastMouseContainsIndex = index
@ -265,8 +286,12 @@ ListView {
visible: mainItem.lastMouseContainsIndex === index || mainItem.currentIndex === index
}
onPressed: {
mainItem.currentIndex = model.index
mainItem.forceActiveFocus()
if (pressedButtons & Qt.RightButton) {
chatroomPopup.open()
} else {
mainItem.currentIndex = model.index
mainItem.forceActiveFocus()
}
}
}
}

View file

@ -19,6 +19,8 @@ Control.Control {
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
onMsgStateChanged: console.log("message state", msgState, chatMessage.core.text)
hoverEnabled: true
signal messageDeletionRequested()
@ -62,9 +64,9 @@ Control.Control {
Layout.preferredWidth: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
spacing: Math.round(2 * DefaultStyle.dp)
topPadding: Math.round(12 * DefaultStyle.dp)
bottomPadding: Math.round(12 * DefaultStyle.dp)
leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp)
bottomPadding: Math.round(6 * DefaultStyle.dp)
leftPadding: Math.round(12 * DefaultStyle.dp)
rightPadding: Math.round(12 * DefaultStyle.dp)
MouseArea {
anchors.fill: parent
@ -110,19 +112,38 @@ Control.Control {
visible: modelData.core.text != undefined
text: modelData.core.text
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: modelData.core.isRemoteMessage ? Text.AlignLeft : Text.AlignRight
color: DefaultStyle.main2_700
font {
pixelSize: Typography.p1.pixelSize
weight: Typography.p1.weight
}
}
Text {
RowLayout {
Layout.alignment: Qt.AlignRight
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false)
color: DefaultStyle.main2_500main
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
Text {
text: UtilsCpp.formatDate(modelData.core.timestamp, true, false)
color: DefaultStyle.main2_500main
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
}
EffectImage {
visible: !mainItem.isRemoteMessage
Layout.preferredWidth: visible ? 14 * DefaultStyle.dp : 0
Layout.preferredHeight: 14 * DefaultStyle.dp
colorizationColor: DefaultStyle.main1_500_main
imageSource: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDelivered
? AppIcons.envelope
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDeliveredToUser
? AppIcons.check
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateNotDelivered
? AppIcons.warningCircle
: mainItem.msgState === LinphoneEnums.ChatMessageState.StateDisplayed
? AppIcons.checks
: ""
}
}
}

View file

@ -20,6 +20,13 @@ ListView {
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
}
onCountChanged: if (atYEnd) {
var index = chatMessageProxy.findFirstUnreadIndex()
mainItem.positionViewAtIndex(index, ListView.End)
var chatMessage = chatMessageProxy.getChatMessageAtIndex(index)
if (chatMessage && !chatMessage.core.isRead) chatMessage.core.lMarkAsRead()
}
Button {
visible: !mainItem.atYEnd

View file

@ -5,10 +5,8 @@ import QtQuick.Controls.Basic
import QtQuick.Layouts
import Qt.labs.platform
// =============================================================================
Window {
id: mainItem
id: mainItem
// ---------------------------------------------------------------------------
@ -17,8 +15,6 @@ Window {
property bool requestActivate: false
//property int flags: Qt.SplashScreen
default property alias _content: content.data
property bool _isOpen: false
signal isOpened()
@ -89,100 +85,4 @@ Window {
}
]
*/
}
/*
Item {
id: wrapper
objectName: '__internalWrapper'
// ---------------------------------------------------------------------------
property alias popupX: window.x
property alias popupY: window.y
property bool requestActivate: false
property int flags: Qt.SplashScreen
readonly property alias popupWidth: window.width
readonly property alias popupHeight: window.height
default property alias _content: content.data
property bool _isOpen: false
signal isOpened()
signal isClosed()
signal dataChanged()
on_ContentChanged: dataChanged(_content)
// ---------------------------------------------------------------------------
function open () {
_isOpen = true;
isOpened();
}
function close () {
_isOpen = false
isClosed()
}
// ---------------------------------------------------------------------------
// No size, no position.
height: 0
width: 0
visible:true
Window {
id: window
objectName: '__internalWindow'
property bool isFrameLess : 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
flags: Qt.BypassWindowManagerHint | (showAsTool?Qt.Tool:Qt.WindowStaysOnTopHint) | Qt.Window | Qt.FramelessWindowHint;
onXChanged: console.log(x)
opacity: 1.0
height: _content[0] != null ? _content[0].height : 0
width: _content[0] != null ? _content[0].width : 0
visible:true
Item {
id: content
anchors.fill:parent
property var $parent: wrapper
}
}
// ---------------------------------------------------------------------------
states: State {
name: 'opening'
when: _isOpen
PropertyChanges {
opacity: 1.0
target: window
}
}
transitions: [
Transition {
from: ''
to: 'opening'
ScriptAction {
script: {
if (wrapper.requestActivate) {
window.requestActivate()
}
}
}
},
Transition {
from: '*'
to: ''
ScriptAction {
script: window.close()
}
}
]
}
*/
}

View file

@ -8,7 +8,7 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
Notification {
id: mainItem
radius: Math.round(20 * DefaultStyle.dp)
radius: Math.round(10 * DefaultStyle.dp)
backgroundColor: DefaultStyle.grey_600
backgroundOpacity: 0.8
overriddenWidth: Math.round(400 * DefaultStyle.dp)

View file

@ -9,7 +9,7 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
Notification {
id: mainItem
radius: Math.round(20 * DefaultStyle.dp)
radius: Math.round(10 * DefaultStyle.dp)
backgroundColor: DefaultStyle.grey_600
backgroundOpacity: 0.8
overriddenWidth: Math.round(400 * DefaultStyle.dp)
@ -26,14 +26,15 @@ Notification {
width: parent.width
leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(18 * DefaultStyle.dp)
topPadding: Math.round(9 * DefaultStyle.dp)
topPadding: Math.round(32 * DefaultStyle.dp)
bottomPadding: Math.round(18 * DefaultStyle.dp)
background: Item{}
contentItem: ColumnLayout {
spacing: Math.round(9 * DefaultStyle.dp)
background: Item {
anchors.fill: parent
RowLayout {
anchors.top: parent.top
anchors.topMargin: Math.round(9 * DefaultStyle.dp)
anchors.horizontalCenter: parent.horizontalCenter
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)
@ -49,65 +50,75 @@ Notification {
}
}
}
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}
Button {
anchors.top: parent.top
anchors.right: parent.right
anchors.topMargin: Math.round(9 * DefaultStyle.dp)
anchors.rightMargin: Math.round(12 * DefaultStyle.dp)
padding: 0
z: mousearea.z + 1
background: Item{anchors.fill: parent}
icon.source: AppIcons.closeX
width: Math.round(14 * DefaultStyle.dp)
height: Math.round(14 * DefaultStyle.dp)
icon.width: Math.round(14 * DefaultStyle.dp)
icon.height: Math.round(14 * DefaultStyle.dp)
contentImageColor: DefaultStyle.grey_0
onPressed: {
mainItem._close()
}
Rectangle {
Layout.fillWidth: true
}
MouseArea {
id: mousearea
anchors.fill: parent
onClicked: {
console.log("notif clicked, open chat")
}
}
}
contentItem: ColumnLayout {
spacing: Math.round(9 * DefaultStyle.dp)
RowLayout {
spacing: Math.round(14 * DefaultStyle.dp)
Avatar {
Layout.preferredWidth: Math.round(60 * DefaultStyle.dp)
Layout.preferredHeight: Math.round(60 * DefaultStyle.dp)
color: DefaultStyle.main2_400
radius: Math.round(5 * 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.grey_100
Layout.fillWidth: true
maximumLineCount: 1
font {
pixelSize: Typography.h4.pixelSize
weight: Typography.h4.weight
capitalization: Font.Capitalize
}
}
Text {
text: mainItem.remoteAddress
color: DefaultStyle.main2_100
Layout.fillWidth: true
maximumLineCount: 1
font {
pixelSize: Typography.p4.pixelSize
weight: Typography.p4.weight
}
}
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
Layout.fillWidth: true
maximumLineCount: 2
color: DefaultStyle.grey_1000
color: DefaultStyle.grey_300
font {
pixelSize: Typography.p1s.pixelSize
weight: Typography.p1s.weight
italic: true
}
}
}

View file

@ -153,6 +153,10 @@ RowLayout {
anchors.fill: parent
radius: Math.round(35 * DefaultStyle.dp)
color: DefaultStyle.grey_0
MouseArea {
anchors.fill: parent
onPressed: sendingTextArea.forceActiveFocus()
}
}
contentItem: RowLayout {
Flickable {
@ -204,6 +208,16 @@ RowLayout {
if (previousText === "" && text !== "") {
mainItem.chat.core.lCompose()
}
previousText = text
}
Keys.onPressed: (event) => {
event.accepted = false
if (UtilsCpp.isEmptyMessage(sendingTextArea.text)) return
if (!(event.modifiers & Qt.ControlModifier) && (event.key == Qt.Key_Return || event.key == Qt.Key_Enter)) {
mainItem.chat.core.lSendTextMessage(sendingTextArea.text)
sendingTextArea.clear()
event.accepted = true
}
}
}
}

View file

@ -131,7 +131,7 @@ AbstractMainPage {
anchors.fill: parent
anchors.rightMargin: Math.round(39 * DefaultStyle.dp)
Text {
visible: chatListView.count === 0
visible: chatListView.count === 0 && chatListView.loading === false
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Math.round(137 * DefaultStyle.dp)
//: "Aucun résultat"

View file

@ -30,7 +30,9 @@ QtObject {
property string profile: "image://internal/user-circle.svg"
property string manageProfile: "image://internal/user-circle-gear.svg"
property string verif_page_image: "image://internal/verif_page_image.svg"
property string envelope: "image://internal/envelope-simple.svg"
property string check: "image://internal/check.svg"
property string checks: "image://internal/checks.svg"
property string dialer: "image://internal/numpad.svg"
property string chiffrement: "image://internal/chiffrement.svg"
property string interoperable: "image://internal/interoperable.svg"