mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-05-03 22:56:49 +00:00
Add Chat reactions
This commit is contained in:
parent
a9812245dc
commit
267d70535d
59 changed files with 1446 additions and 70 deletions
|
|
@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 5.2.0 - undefined
|
||||
|
||||
### Fixed
|
||||
- Download path and emojis size settings
|
||||
- Mac emoji font.
|
||||
|
||||
### Added
|
||||
- Chat reactions
|
||||
|
||||
## 5.1.2 - 2023-08-25
|
||||
|
||||
|
|
|
|||
|
|
@ -242,6 +242,9 @@ set(SOURCES
|
|||
src/components/chat-events/ChatMessageListener.cpp
|
||||
src/components/chat-events/ChatMessageModel.cpp
|
||||
src/components/chat-events/ChatNoticeModel.cpp
|
||||
src/components/chat-reaction/ChatReactionModel.cpp
|
||||
src/components/chat-reaction/ChatReactionListModel.cpp
|
||||
src/components/chat-reaction/ChatReactionProxyModel.cpp
|
||||
src/components/chat-room/ChatRoomInitializer.cpp
|
||||
src/components/chat-room/ChatRoomListener.cpp
|
||||
src/components/chat-room/ChatRoomModel.cpp
|
||||
|
|
@ -384,6 +387,9 @@ set(HEADERS
|
|||
src/components/chat-events/ChatMessageListener.hpp
|
||||
src/components/chat-events/ChatMessageModel.hpp
|
||||
src/components/chat-events/ChatNoticeModel.hpp
|
||||
src/components/chat-reaction/ChatReactionModel.hpp
|
||||
src/components/chat-reaction/ChatReactionListModel.hpp
|
||||
src/components/chat-reaction/ChatReactionProxyModel.hpp
|
||||
src/components/chat-room/ChatRoomInitializer.hpp
|
||||
src/components/chat-room/ChatRoomListener.hpp
|
||||
src/components/chat-room/ChatRoomModel.hpp
|
||||
|
|
|
|||
|
|
@ -758,6 +758,18 @@ Adresa URL není nakonfigurována.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ Server url ikke konfigureret.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ Server URL ist nicht konfiguriert.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
@ -1596,7 +1607,7 @@ Server URL ist nicht konfiguriert.</translation>
|
|||
<message>
|
||||
<source>incallPauseWarning</source>
|
||||
<extracomment>'You are currently out of the conference.' : Pause message in video conference.</extracomment>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">Sie haben den Anruf unterbrochen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>incallPauseHint</source>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ Server URL not configured.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation>
|
||||
<numerusform>%1<br>reaction</numerusform>
|
||||
<numerusform>%1<br>reactions</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ URL del servidor no configurada.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ URL du serveur non configurée.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation>
|
||||
<numerusform>%1<br>réaction</numerusform>
|
||||
<numerusform>%1<br>réactions</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -746,6 +746,16 @@ A kiszolgáló URL-je nincs konfigurálva.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ URL del server non configurato.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -746,6 +746,16 @@
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -758,6 +758,18 @@ Nesukonfigūruotas serverio url.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ URL do servidor não configurado.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -758,6 +758,18 @@
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -752,6 +752,17 @@ Serverwebbadressen är inte konfigurerad.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -746,6 +746,16 @@ Sunucu url'si yapılandırılmadı.</translation>
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -758,6 +758,18 @@
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -746,6 +746,16 @@
|
|||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReactionsDetails</name>
|
||||
<message numerus="yes">
|
||||
<source>reactionsCount</source>
|
||||
<extracomment>"%1<br>reactions" : count of all chat reactions with a jump line between count and text.</extracomment>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ChatReplyMessage</name>
|
||||
<message>
|
||||
|
|
|
|||
|
|
@ -346,6 +346,7 @@
|
|||
<file>ui/modules/Linphone/Chat/ChatFilePreview.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatForwardMessage.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatMessagePreview.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatReactionsDetails.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatReplyMessage.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatReplyPreview.qml</file>
|
||||
<file>ui/modules/Linphone/Chat/ChatTextMessage.qml</file>
|
||||
|
|
@ -403,6 +404,7 @@
|
|||
<file>ui/modules/Linphone/Styles/Chat/ChatFilePreviewStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Chat/ChatCalendarMessageStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Chat/ChatForwardMessageStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Chat/ChatReactionsDetailsStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Chat/ChatReplyMessageStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Codecs/CodecsViewerStyle.qml</file>
|
||||
<file>ui/modules/Linphone/Styles/Contact/AvatarStyle.qml</file>
|
||||
|
|
|
|||
|
|
@ -722,6 +722,7 @@ void App::registerTypes () {
|
|||
registerType<CallsListProxyModel>("CallsListProxyModel");
|
||||
registerType<Camera>("Camera");
|
||||
registerType<ChatRoomProxyModel>("ChatRoomProxyModel");
|
||||
registerType<ChatReactionProxyModel>("ChatReactionProxyModel");
|
||||
registerType<ConferenceHelperModel>("ConferenceHelperModel");
|
||||
registerType<ConferenceProxyModel>("ConferenceProxyModel");
|
||||
registerType<ConferenceInfoModel>("ConferenceInfoModel");
|
||||
|
|
@ -762,6 +763,7 @@ void App::registerTypes () {
|
|||
registerUncreatableType<ChatCallModel>("ChatCallModel");
|
||||
registerUncreatableType<ChatMessageModel>("ChatMessageModel");
|
||||
registerUncreatableType<ChatNoticeModel>("ChatNoticeModel");
|
||||
registerUncreatableType<ChatReactionListModel>("ChatReactionListModel");
|
||||
registerUncreatableType<ChatRoomModel>("ChatRoomModel");
|
||||
registerUncreatableType<ColorModel>("ColorModel");
|
||||
registerUncreatableType<ImageModel>("ImageModel");
|
||||
|
|
|
|||
|
|
@ -112,11 +112,11 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
virtual void clearData(){
|
||||
virtual void clearData() override{
|
||||
mList.clear();
|
||||
}
|
||||
|
||||
virtual void resetData(){
|
||||
virtual void resetData() override{
|
||||
beginResetModel();
|
||||
clearData();
|
||||
endResetModel();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "components/chat-events/ChatCallModel.hpp"
|
||||
#include "components/chat-events/ChatMessageModel.hpp"
|
||||
#include "components/chat-events/ChatNoticeModel.hpp"
|
||||
#include "components/chat-reaction/ChatReactionProxyModel.hpp"
|
||||
#include "chat-room/ChatRoomProxyModel.hpp"
|
||||
#include "codecs/AudioCodecsModel.hpp"
|
||||
#include "codecs/VideoCodecsModel.hpp"
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ void ChatMessageListener::onFileTransferProgressIndication (const std::shared_pt
|
|||
void ChatMessageListener::onMsgStateChanged (const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state){
|
||||
emit msgStateChanged(message, state);
|
||||
}
|
||||
void ChatMessageListener::onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction) {
|
||||
emit newMessageReaction(message, reaction);
|
||||
}
|
||||
void ChatMessageListener::onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ParticipantImdnState> & state){
|
||||
emit participantImdnStateChanged(message, state);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public:
|
|||
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;
|
||||
virtual void onFileTransferProgressIndication (const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &, size_t offset, size_t) override;
|
||||
virtual void onMsgStateChanged (const std::shared_ptr<linphone::ChatMessage> &message, linphone::ChatMessage::State state) override;
|
||||
virtual void onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction) override;
|
||||
virtual void onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ParticipantImdnState> & state) override;
|
||||
virtual void onEphemeralMessageTimerStarted(const std::shared_ptr<linphone::ChatMessage> & message) override;
|
||||
virtual void onEphemeralMessageDeleted(const std::shared_ptr<linphone::ChatMessage> & message) override;
|
||||
|
|
@ -50,6 +51,7 @@ signals:
|
|||
std::shared_ptr<linphone::Buffer> fileTransferSend (const std::shared_ptr<linphone::ChatMessage> &,const std::shared_ptr<linphone::Content> &,size_t,size_t);
|
||||
void fileTransferProgressIndication (const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &, size_t offset, size_t);
|
||||
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 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);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@
|
|||
|
||||
#include "app/App.hpp"
|
||||
#include "app/paths/Paths.hpp"
|
||||
#include "components/chat-reaction/ChatReactionModel.hpp"
|
||||
#include "components/chat-reaction/ChatReactionListModel.hpp"
|
||||
#include "components/contact/ContactModel.hpp"
|
||||
#include "components/contacts/ContactsListModel.hpp"
|
||||
#include "components/content/ContentListModel.hpp"
|
||||
|
|
@ -61,6 +63,7 @@ void ChatMessageModel::connectTo(ChatMessageListener * listener){
|
|||
connect(listener, &ChatMessageListener::fileTransferSend, this, &ChatMessageModel::onFileTransferSend);
|
||||
connect(listener, &ChatMessageListener::fileTransferProgressIndication, this, &ChatMessageModel::onFileTransferProgressIndication);
|
||||
connect(listener, &ChatMessageListener::msgStateChanged, this, &ChatMessageModel::onMsgStateChanged);
|
||||
connect(listener, &ChatMessageListener::newMessageReaction, this, &ChatMessageModel::onNewMessageReaction);
|
||||
connect(listener, &ChatMessageListener::participantImdnStateChanged, this, &ChatMessageModel::onParticipantImdnStateChanged);
|
||||
connect(listener, &ChatMessageListener::ephemeralMessageTimerStarted, this, &ChatMessageModel::onEphemeralMessageTimerStarted);
|
||||
connect(listener, &ChatMessageListener::ephemeralMessageDeleted, this, &ChatMessageModel::onEphemeralMessageDeleted);
|
||||
|
|
@ -95,6 +98,7 @@ ChatMessageModel::ChatMessageModel ( std::shared_ptr<linphone::ChatMessage> chat
|
|||
mWasDownloaded = false;
|
||||
|
||||
mContentListModel = QSharedPointer<ContentListModel>::create(this);
|
||||
mChatReactionListModel = QSharedPointer<ChatReactionListModel>::create(this);
|
||||
}
|
||||
|
||||
ChatMessageModel::~ChatMessageModel(){
|
||||
|
|
@ -188,6 +192,10 @@ QSharedPointer<ContentListModel> ChatMessageModel::getContents() const{
|
|||
return mContentListModel;
|
||||
}
|
||||
|
||||
QSharedPointer<ChatReactionListModel> ChatMessageModel::getChatReactions() const {
|
||||
return mChatReactionListModel;
|
||||
}
|
||||
|
||||
bool ChatMessageModel::isReply() const{
|
||||
return mChatMessage && mChatMessage->isReply();
|
||||
}
|
||||
|
|
@ -247,6 +255,16 @@ void ChatMessageModel::resendMessage (){
|
|||
}
|
||||
}
|
||||
|
||||
void ChatMessageModel::sendChatReaction(const QString& reaction){
|
||||
auto chatReaction = mChatMessage->createReaction(Utils::appStringToCoreString(reaction));
|
||||
if( mChatReactionListModel->exists(chatReaction)) {
|
||||
chatReaction = mChatMessage->createReaction("");
|
||||
return; // TODO : remove return when sending empty emoji will be supported.
|
||||
}
|
||||
chatReaction->send();
|
||||
mChatReactionListModel->updateChatReaction(chatReaction);
|
||||
}
|
||||
|
||||
void ChatMessageModel::deleteEvent(){
|
||||
if (mChatMessage && mChatMessage->getFileTransferInformation()) {
|
||||
mChatMessage->cancelFileTransfer();
|
||||
|
|
@ -307,6 +325,11 @@ void ChatMessageModel::onMsgStateChanged (const std::shared_ptr<linphone::ChatMe
|
|||
}
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
void ChatMessageModel::onNewMessageReaction(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction){
|
||||
mChatReactionListModel->updateChatReaction(reaction);
|
||||
}
|
||||
|
||||
void ChatMessageModel::onParticipantImdnStateChanged(const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ParticipantImdnState> & state){
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
|
||||
class ChatMessageModel;
|
||||
class ChatMessageListener;
|
||||
class ChatReactionModel;
|
||||
class ChatReactionListModel;
|
||||
class ParticipantImdnStateProxyModel;
|
||||
class ParticipantImdnStateListModel;
|
||||
class ContentModel;
|
||||
|
|
@ -93,6 +95,7 @@ public:
|
|||
Q_INVOKABLE ParticipantImdnStateProxyModel * getProxyImdnStates();
|
||||
QSharedPointer<ParticipantImdnStateListModel> getParticipantImdnStates() const;
|
||||
QSharedPointer<ContentListModel> getContents() const;
|
||||
QSharedPointer<ChatReactionListModel> getChatReactions() const;
|
||||
|
||||
bool isReply() const;
|
||||
ChatMessageModel * getReplyChatMessageModel() const;
|
||||
|
|
@ -110,6 +113,7 @@ public:
|
|||
//----------------------------------------------------------------------------
|
||||
|
||||
Q_INVOKABLE void resendMessage ();
|
||||
Q_INVOKABLE void sendChatReaction(const QString& reaction);
|
||||
|
||||
virtual void deleteEvent() override;
|
||||
void updateFileTransferInformation();
|
||||
|
|
@ -121,6 +125,7 @@ public:
|
|||
std::shared_ptr<linphone::Buffer> onFileTransferSend (const std::shared_ptr<linphone::ChatMessage> &,const std::shared_ptr<linphone::Content> &,size_t,size_t);
|
||||
void onFileTransferProgressIndication (const std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &, size_t offset, size_t);
|
||||
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 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);
|
||||
|
|
@ -152,6 +157,7 @@ private:
|
|||
QSharedPointer<ContentModel> mFileTransfertContent;
|
||||
QSharedPointer<ParticipantImdnStateListModel> mParticipantImdnStateListModel;
|
||||
QSharedPointer<ChatMessageModel> mReplyChatMessageModel;
|
||||
QSharedPointer<ChatReactionListModel> mChatReactionListModel;
|
||||
|
||||
QString mFromDisplayNameCache;
|
||||
QString fromDisplayNameReplyMessage;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatReactionListModel.hpp"
|
||||
|
||||
#include "ChatReactionModel.hpp"
|
||||
#include "components/chat-events/ChatMessageModel.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
ChatReactionListModel::ChatReactionListModel (ChatMessageModel * message, QObject* parent) : ProxyAbstractListModel<QVariantMap>(parent) {
|
||||
mParent = message;
|
||||
setChatMessageModel(message);
|
||||
}
|
||||
|
||||
void ChatReactionListModel::setChatMessageModel(ChatMessageModel * message) {
|
||||
if(message){
|
||||
auto reactions = message->getChatMessage()->getReactions();
|
||||
mReactions.clear();
|
||||
mBodies.clear();
|
||||
for(auto reaction : reactions){
|
||||
auto reactionModel = QSharedPointer<ChatReactionModel>::create(reaction);
|
||||
auto body = reactionModel->getBody();
|
||||
if(!body.isEmpty()) {
|
||||
mReactions[reactionModel->getFromAddress()] = reactionModel;
|
||||
mBodies[reactionModel->getBody()].push_back(reactionModel);
|
||||
}
|
||||
}
|
||||
updateList();
|
||||
}
|
||||
}
|
||||
|
||||
int ChatReactionListModel::count(){
|
||||
return mList.count();
|
||||
}
|
||||
|
||||
int ChatReactionListModel::getChatReactionCount(const QString& emoji) const {
|
||||
if(emoji.isEmpty())
|
||||
return mReactions.size();
|
||||
else if(mBodies.contains(emoji))
|
||||
return mBodies[emoji].size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
QSharedPointer<ChatReactionModel> ChatReactionListModel::add(std::shared_ptr<linphone::ChatMessageReaction> reaction){
|
||||
auto reactionModel = QSharedPointer<ChatReactionModel>::create(reaction);
|
||||
//ProxyListModel::add(reactionModel);
|
||||
emit chatReactionsChanged();
|
||||
return reactionModel;
|
||||
}
|
||||
|
||||
|
||||
void ChatReactionListModel::remove(ChatReactionModel * model){/*
|
||||
int count = 0;
|
||||
for(auto it = mList.begin() ; it != mList.end() ; ++count, ++it) {
|
||||
if( it->get() == model) {
|
||||
removeRow(count, QModelIndex());
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void ChatReactionListModel::clear(){
|
||||
resetData();
|
||||
}
|
||||
|
||||
/*
|
||||
QSharedPointer<ChatReactionModel> ChatReactionListModel::getChatReactionModel(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction){
|
||||
for(auto item : mList){
|
||||
auto c = item.objectCast<ChatReactionModel>();
|
||||
if(c->get() == content)
|
||||
return c;
|
||||
}
|
||||
if(content->isFileTransfer() || content->isFile() || content->isFileEncrypted()){
|
||||
for(auto item : mList){// Content object can be different for file (like while data transfer)
|
||||
auto c = item.objectCast<ContentModel>();
|
||||
if(c->getContent()->getFilePath() == content->getFilePath())
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
*/
|
||||
void ChatReactionListModel::updateChatReaction(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction) {
|
||||
QString address = Utils::coreStringToAppString(reaction->getFromAddress()->asStringUriOnly());
|
||||
auto itReaction = mReactions.find(address);
|
||||
int oldReactionCount = mReactions.size();
|
||||
if( itReaction == mReactions.end()) {// New
|
||||
auto reactionModel = QSharedPointer<ChatReactionModel>::create(reaction);
|
||||
auto body = reactionModel->getBody();
|
||||
if(body.isEmpty()) {
|
||||
mReactions.remove(reactionModel->getFromAddress());
|
||||
// TODO: optimize remove
|
||||
mBodies.clear();
|
||||
for(auto it : mReactions)
|
||||
mBodies[it->getBody()].push_back(it);
|
||||
}else{
|
||||
mReactions[reactionModel->getFromAddress()] = reactionModel;
|
||||
mBodies[reactionModel->getBody()].push_back(reactionModel);
|
||||
}
|
||||
}else{// Update
|
||||
(*itReaction)->setBody(Utils::coreStringToAppString(reaction->getBody()));
|
||||
// TODO: optimize update with a swap
|
||||
mBodies.clear();
|
||||
for(auto it : mReactions)
|
||||
mBodies[it->getBody()].push_back(it);
|
||||
}
|
||||
updateList();
|
||||
if(oldReactionCount != mReactions.size())
|
||||
emit chatReactionCountChanged();
|
||||
}
|
||||
void ChatReactionListModel::updateList(){
|
||||
QList<QVariantMap> data;
|
||||
if(mGroupBy == EMOJIES){
|
||||
for(auto it = mBodies.begin() ; it != mBodies.end() ; ++it) {
|
||||
QVariantMap emoji;
|
||||
emoji["body"] = it.key();
|
||||
emoji["reactionsCount"] = it->size();
|
||||
data << emoji;
|
||||
}
|
||||
}else{
|
||||
for(auto reaction : mReactions){
|
||||
QVariantMap react;
|
||||
react["reaction"] = QVariant::fromValue(reaction.get());
|
||||
data << react;
|
||||
}
|
||||
}
|
||||
resetData();
|
||||
ProxyAbstractListModel<QVariantMap>::add(data);
|
||||
emit chatReactionsChanged();
|
||||
}
|
||||
|
||||
bool ChatReactionListModel::exists(std::shared_ptr<linphone::ChatMessageReaction> reaction) const {
|
||||
QString address = Utils::coreStringToAppString(reaction->getFromAddress()->asStringUriOnly());
|
||||
auto itReaction = mReactions.find(address);
|
||||
if(itReaction != mReactions.end())
|
||||
return (*itReaction)->getBody() == Utils::coreStringToAppString(reaction->getBody());
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChatReactionListModel::updateChatReaction(std::shared_ptr<linphone::ChatMessageReaction> oldReaction, std::shared_ptr<linphone::ChatMessageReaction> newReaction) {
|
||||
|
||||
|
||||
}
|
||||
void ChatReactionListModel::updateChatReaction(ChatMessageModel * messageModel) {
|
||||
|
||||
}
|
||||
|
||||
ChatReactionListModel::GROUP_BY_TYPE ChatReactionListModel::getGroupBy() const {
|
||||
return mGroupBy;
|
||||
}
|
||||
|
||||
void ChatReactionListModel::setGroupBy(ChatReactionListModel::GROUP_BY_TYPE mode) {
|
||||
if( mGroupBy != mode ) {
|
||||
mGroupBy = mode;
|
||||
updateList();
|
||||
emit groupByChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
void ContentListModel::updateContent(std::shared_ptr<linphone::Content> oldContent, std::shared_ptr<linphone::Content> newContent){
|
||||
int row = 0;
|
||||
for(auto content = mList.begin() ; content != mList.end() ; ++content, ++row){
|
||||
auto contentModel = content->objectCast<ContentModel>();
|
||||
if( contentModel->getContent() == oldContent){
|
||||
mList.replace(row, QSharedPointer<ContentModel>::create(newContent, contentModel->getChatMessageModel()));
|
||||
emit dataChanged(index(row,0), index(row,0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContentListModel::updateContents(ChatMessageModel * messageModel){
|
||||
std::list<std::shared_ptr<linphone::Content>> contents = messageModel->getChatMessage()->getContents() ;
|
||||
int count = 0;
|
||||
beginResetModel();
|
||||
for(auto content : contents){
|
||||
if( count >= mList.size()){// New content
|
||||
mList.insert(count, QSharedPointer<ContentModel>::create(content, messageModel));
|
||||
}else if(mList.at(count).objectCast<ContentModel>()->getContent() != content){ // This content is not at its place
|
||||
int c = count + 1;
|
||||
while( c < mList.size() && mList.at(c).objectCast<ContentModel>()->getContent() != content)
|
||||
++c;
|
||||
if( c < mList.size()){// Found => swap position
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0)
|
||||
mList.swap(count, c);
|
||||
#else
|
||||
mList.swapItemsAt(count, c);
|
||||
#endif
|
||||
}else{// content is new
|
||||
mList.insert(count, QSharedPointer<ContentModel>::create(content, messageModel));
|
||||
}
|
||||
}
|
||||
++count;
|
||||
}
|
||||
if(count < mList.size())// Remove all old contents
|
||||
mList.erase(mList.begin()+count, mList.end());
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ContentListModel::updateAllTransferData(){
|
||||
emit updateTransferDataRequested();
|
||||
}
|
||||
|
||||
void ContentListModel::downloaded(){
|
||||
for(auto content : mList)
|
||||
content.objectCast<ContentModel>()->createThumbnail();
|
||||
}*/
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_REACTION_LIST_MODEL_H_
|
||||
#define CHAT_REACTION_LIST_MODEL_H_
|
||||
|
||||
#include <linphone++/linphone.hh>
|
||||
#include "app/proxyModel/ProxyAbstractListModel.hpp"
|
||||
#include <QDateTime>
|
||||
|
||||
class ChatReactionModel;
|
||||
class ChatMessageModel;
|
||||
|
||||
class ChatReactionListModel : public ProxyAbstractListModel<QVariantMap> {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef enum{
|
||||
EMOJIES,
|
||||
REACTIONS
|
||||
}GROUP_BY_TYPE;
|
||||
Q_ENUM(GROUP_BY_TYPE)
|
||||
|
||||
ChatReactionListModel (ChatMessageModel * message = nullptr, QObject * parent = nullptr);
|
||||
void setChatMessageModel(ChatMessageModel * message);
|
||||
|
||||
int count();
|
||||
int getChatReactionCount(const QString& emoji = "")const;
|
||||
|
||||
QSharedPointer<ChatReactionModel> add(std::shared_ptr<linphone::ChatMessageReaction> reaction);
|
||||
Q_INVOKABLE void remove(ChatReactionModel * model);
|
||||
|
||||
void clear();
|
||||
|
||||
ChatReactionListModel::GROUP_BY_TYPE getGroupBy() const;
|
||||
void setGroupBy(ChatReactionListModel::GROUP_BY_TYPE mode);
|
||||
|
||||
//QSharedPointer<ChatReactionModel> getChatReactionModel(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction);
|
||||
|
||||
bool exists(std::shared_ptr<linphone::ChatMessageReaction> reaction) const;
|
||||
|
||||
void updateChatReaction(std::shared_ptr<linphone::ChatMessageReaction> oldReaction, std::shared_ptr<linphone::ChatMessageReaction> newReaction);
|
||||
void updateChatReaction(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction);
|
||||
void updateChatReaction(ChatMessageModel * messageModel);
|
||||
|
||||
void updateList();
|
||||
signals:
|
||||
void chatReactionsChanged();
|
||||
void chatReactionCountChanged();
|
||||
void groupByChanged();
|
||||
|
||||
private:
|
||||
ChatMessageModel * mParent;
|
||||
QMap<QString, QSharedPointer<ChatReactionModel>> mReactions;
|
||||
QMap<QString, QVector<QSharedPointer<ChatReactionModel>>> mBodies;
|
||||
GROUP_BY_TYPE mGroupBy = EMOJIES;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<ChatReactionListModel>)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatReactionModel.hpp"
|
||||
|
||||
#include "app/App.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
ChatReactionModel::ChatReactionModel(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction) {
|
||||
App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE
|
||||
mBody = Utils::coreStringToAppString(reaction->getBody());
|
||||
mFromAddress = Utils::coreStringToAppString(reaction->getFromAddress()->asStringUriOnly());
|
||||
}
|
||||
|
||||
QString ChatReactionModel::getBody() const {
|
||||
return mBody;
|
||||
}
|
||||
|
||||
void ChatReactionModel::setBody(const QString& body) {
|
||||
mBody = body;
|
||||
emit bodyChanged();
|
||||
}
|
||||
|
||||
QString ChatReactionModel::getFromAddress() const {
|
||||
return mFromAddress;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_REACTION_MODEL_H_
|
||||
#define CHAT_REACTION_MODEL_H_
|
||||
|
||||
#include <linphone++/linphone.hh>
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
class ChatReactionModel : public QObject {
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChatReactionModel(const std::shared_ptr<const linphone::ChatMessageReaction>& reaction);
|
||||
|
||||
Q_PROPERTY(QString body READ getBody WRITE setBody NOTIFY bodyChanged)
|
||||
Q_PROPERTY(QString fromAddress READ getFromAddress CONSTANT)
|
||||
|
||||
QString getBody() const;
|
||||
void setBody(const QString& body);
|
||||
|
||||
QString getFromAddress() const;
|
||||
|
||||
signals:
|
||||
void bodyChanged();
|
||||
|
||||
private:
|
||||
QString mBody;
|
||||
QString mFromAddress;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(QSharedPointer<ChatReactionModel>)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ChatReactionProxyModel.hpp"
|
||||
|
||||
#include "ChatReactionListModel.hpp"
|
||||
#include "ChatReactionModel.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
ChatReactionProxyModel::ChatReactionProxyModel (QObject * parent) : SortFilterProxyModel(parent){
|
||||
mContents = QSharedPointer<ChatReactionListModel>::create();
|
||||
connect(mContents.get(), &ChatReactionListModel::chatReactionCountChanged, this, &ChatReactionProxyModel::chatReactionCountChanged);
|
||||
connect(mContents.get(), &ChatReactionListModel::groupByChanged, this, &ChatReactionProxyModel::groupByChanged);
|
||||
setSourceModel(mContents.get());
|
||||
sort(0);
|
||||
}
|
||||
|
||||
ChatMessageModel * ChatReactionProxyModel::getChatMessageModel() const{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ChatReactionProxyModel::setChatMessageModel(ChatMessageModel * message){
|
||||
setChatMessageModel(message, ChatReactionListModel::GROUP_BY_TYPE::EMOJIES);
|
||||
}
|
||||
|
||||
void ChatReactionProxyModel::setChatMessageModel(ChatMessageModel * message, ChatReactionListModel::GROUP_BY_TYPE groupByMode) {
|
||||
if(message){
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
model->setChatMessageModel(message);
|
||||
model->setGroupBy(groupByMode);
|
||||
}
|
||||
emit chatMessageModelChanged();
|
||||
emit chatReactionCountChanged();
|
||||
}
|
||||
|
||||
int ChatReactionProxyModel::getChatReactionCount() const {
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
if(model)
|
||||
return model->getChatReactionCount();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ChatReactionProxyModel::getChatReactionCount(const QString& emoji) const {
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
if(model)
|
||||
return model->getChatReactionCount(emoji);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
ChatReactionListModel::GROUP_BY_TYPE ChatReactionProxyModel::getGroupBy() const {
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
return model ? model->getGroupBy() : ChatReactionListModel::GROUP_BY_TYPE::EMOJIES;
|
||||
}
|
||||
|
||||
void ChatReactionProxyModel::setGroupBy(ChatReactionListModel::GROUP_BY_TYPE mode) {
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
if(model)
|
||||
model->setGroupBy(mode);
|
||||
}
|
||||
|
||||
QString ChatReactionProxyModel::getFilter() const {
|
||||
return mFilter;
|
||||
}
|
||||
|
||||
void ChatReactionProxyModel::setFilter(const QString& filter) {
|
||||
if(mFilter != filter) {
|
||||
mFilter = filter;
|
||||
emit filterChanged();
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
void ChatReactionProxyModel::setContentListModel(ContentListModel * model){
|
||||
setSourceModel(model);
|
||||
sort(0);
|
||||
emit chatMessageModelChanged();
|
||||
}
|
||||
*/
|
||||
|
||||
bool ChatReactionProxyModel::filterAcceptsRow (
|
||||
int sourceRow,
|
||||
const QModelIndex &sourceParent
|
||||
) const {
|
||||
|
||||
bool show = false;
|
||||
|
||||
if (mFilter.isEmpty())
|
||||
show = true;
|
||||
else{
|
||||
auto model = qobject_cast<ChatReactionListModel*>(sourceModel());
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, QModelIndex());
|
||||
auto reaction = sourceModel()->data(index).value<QVariantMap>();
|
||||
|
||||
if( model->getGroupBy() == ChatReactionListModel::GROUP_BY_TYPE::REACTIONS) {
|
||||
if( mFilter == reaction["reaction"].value<ChatReactionModel*>()->getBody())
|
||||
show = true;
|
||||
}
|
||||
}
|
||||
return show;
|
||||
}
|
||||
|
||||
/*
|
||||
bool ChatReactionProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
|
||||
const ContentModel *contentA = sourceModel()->data(left).value<ContentModel *>();
|
||||
const ContentModel *contentB = sourceModel()->data(right).value<ContentModel *>();
|
||||
bool aIsForward = contentA->getChatMessageModel() && contentA->getChatMessageModel()->isForward();
|
||||
bool aIsReply = contentA->getChatMessageModel() && contentA->getChatMessageModel()->isReply();
|
||||
bool aIsVoiceRecording = contentA->isVoiceRecording();
|
||||
bool aIsFile = contentA->isFile() || contentA->isFileEncrypted() || contentA->isFileTransfer();
|
||||
bool aIsText = contentA->isText() ;
|
||||
bool bIsForward = contentB->getChatMessageModel() && contentB->getChatMessageModel()->isForward();
|
||||
bool bIsReply = contentB->getChatMessageModel() && contentB->getChatMessageModel()->isReply();
|
||||
bool bIsVoiceRecording = contentB->isVoiceRecording();
|
||||
bool bIsFile = contentB->isFile() || contentB->isFileEncrypted() || contentB->isFileTransfer();
|
||||
bool bIsText = contentB->isText() ;
|
||||
|
||||
return !bIsForward && (aIsForward
|
||||
|| !bIsReply && (aIsReply
|
||||
|| !bIsVoiceRecording && (aIsVoiceRecording
|
||||
|| !bIsFile && (aIsFile
|
||||
|| aIsText && !bIsText
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
*/
|
||||
/*
|
||||
void ChatReactionProxyModel::remove(ContentModel * model){
|
||||
qobject_cast<ContentListModel*>(sourceModel())->remove(model);
|
||||
}
|
||||
|
||||
void ChatReactionProxyModel::clear(){
|
||||
qobject_cast<ContentListModel*>(sourceModel())->clear();
|
||||
}
|
||||
|
||||
ContentProxyModel::FilterContentType ChatReactionProxyModel::getFilter() const{
|
||||
return mFilter;
|
||||
}
|
||||
void ChatReactionProxyModel::setFilter(const FilterContentType& contentType){
|
||||
if(contentType != mFilter){
|
||||
mFilter = contentType;
|
||||
emit filterChanged();
|
||||
invalidate();
|
||||
}
|
||||
}*/
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2023 Belledonne Communications SARL.
|
||||
*
|
||||
* This file is part of linphone-desktop
|
||||
* (see https://www.linphone.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CHAT_REACTION_PROXY_MODEL_H_
|
||||
#define CHAT_REACTION_PROXY_MODEL_H_
|
||||
|
||||
#include "app/proxyModel/SortFilterProxyModel.hpp"
|
||||
#include "components/chat-events/ChatMessageModel.hpp"
|
||||
#include "ChatReactionListModel.hpp"
|
||||
|
||||
// =============================================================================
|
||||
|
||||
class ChatReactionProxyModel : public SortFilterProxyModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ChatReactionProxyModel (QObject *parent = nullptr);
|
||||
Q_PROPERTY(ChatMessageModel * chatMessageModel READ getChatMessageModel WRITE setChatMessageModel NOTIFY chatMessageModelChanged)
|
||||
Q_PROPERTY(int reactionCount READ getChatReactionCount NOTIFY chatReactionCountChanged)
|
||||
Q_PROPERTY(ChatReactionListModel::GROUP_BY_TYPE groupBy READ getGroupBy WRITE setGroupBy NOTIFY groupByChanged)
|
||||
Q_PROPERTY(QString filter READ getFilter WRITE setFilter NOTIFY filterChanged)
|
||||
/*
|
||||
Q_PROPERTY(FilterContentType filter READ getFilter WRITE setFilter NOTIFY filterChanged)
|
||||
|
||||
enum FilterContentType {
|
||||
All,
|
||||
File,
|
||||
Text,
|
||||
Voice,
|
||||
Conference,
|
||||
Unknown
|
||||
};
|
||||
Q_ENUM(FilterContentType)
|
||||
*/
|
||||
ChatMessageModel * getChatMessageModel() const;
|
||||
void setChatMessageModel(ChatMessageModel * message);
|
||||
Q_INVOKABLE void setChatMessageModel(ChatMessageModel * message, ChatReactionListModel::GROUP_BY_TYPE groupByMode);
|
||||
|
||||
int getChatReactionCount() const;
|
||||
Q_INVOKABLE int getChatReactionCount(const QString& emoji) const;
|
||||
|
||||
ChatReactionListModel::GROUP_BY_TYPE getGroupBy() const;
|
||||
void setGroupBy(ChatReactionListModel::GROUP_BY_TYPE mode);
|
||||
|
||||
QString getFilter() const;
|
||||
void setFilter(const QString& filter);
|
||||
//Q_INVOKABLE void setContentListModel(ContentListModel * model);
|
||||
//Q_INVOKABLE void addFile(const QString& path);
|
||||
//Q_INVOKABLE void remove(ContentModel * model);
|
||||
//Q_INVOKABLE void clear();
|
||||
|
||||
signals:
|
||||
void chatMessageModelChanged();
|
||||
void chatReactionCountChanged();
|
||||
void groupByChanged();
|
||||
void filterChanged();
|
||||
|
||||
|
||||
protected:
|
||||
QSharedPointer<ChatReactionListModel> mContents;
|
||||
virtual bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
//virtual bool lessThan (const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
//std::shared_ptr<ChatReacListModel> mContents;
|
||||
QString mFilter = "";
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -867,24 +867,39 @@ void ChatRoomModel::updateNewMessageNotice(const int& count){
|
|||
|
||||
int ChatRoomModel::loadTillMessage(ChatMessageModel * message){
|
||||
if( message){
|
||||
qDebug() << "Load history till message : " << message->getChatMessage()->getMessageId().c_str();
|
||||
auto linphoneMessage = message->getChatMessage();
|
||||
return loadTillMessage(message);
|
||||
}else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ChatRoomModel::loadTillMessage(std::shared_ptr<linphone::ChatMessage> linphoneMessage){
|
||||
if(linphoneMessage)
|
||||
return loadTillMessageId(Utils::coreStringToAppString(linphoneMessage->getMessageId()));
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ChatRoomModel::loadTillMessageId(const QString& messageId){
|
||||
if(!messageId.isEmpty()){
|
||||
std::string lMessageId = Utils::appStringToCoreString(messageId);
|
||||
qDebug() << "Load history till message : " << messageId;
|
||||
// First find on current list
|
||||
auto entry = std::find_if(mList.begin(), mList.end(), [linphoneMessage](const QSharedPointer<QObject>& entry ){
|
||||
auto entry = std::find_if(mList.begin(), mList.end(), [lMessageId](const QSharedPointer<QObject>& entry ){
|
||||
auto chatEventEntry = entry.objectCast<ChatEvent>();
|
||||
return chatEventEntry->mType == ChatRoomModel::EntryType::MessageEntry && chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage() == linphoneMessage;
|
||||
return chatEventEntry->mType == ChatRoomModel::EntryType::MessageEntry && chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage()->getMessageId() == lMessageId;
|
||||
});
|
||||
// if not find, load more entries and find it in new entries.
|
||||
if( entry == mList.end()){
|
||||
mPostModelChangedEvents = false;
|
||||
beginResetModel();
|
||||
int newEntries = loadMoreEntries();
|
||||
while( newEntries > 0){// no more new entries
|
||||
while( newEntries > 0){// at 0 = no more new entries
|
||||
int entryCount = 0;
|
||||
entry = mList.begin();
|
||||
auto chatEventEntry = entry->objectCast<ChatEvent>();
|
||||
while(entryCount < newEntries &&
|
||||
(chatEventEntry->mType != ChatRoomModel::EntryType::MessageEntry || chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage() != linphoneMessage)
|
||||
(chatEventEntry->mType != ChatRoomModel::EntryType::MessageEntry || chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage()->getMessageId() != lMessageId)
|
||||
){
|
||||
++entryCount;
|
||||
++entry;
|
||||
|
|
@ -892,36 +907,43 @@ int ChatRoomModel::loadTillMessage(ChatMessageModel * message){
|
|||
chatEventEntry = entry->objectCast<ChatEvent>();
|
||||
}
|
||||
if( entryCount < newEntries){// We got it
|
||||
qDebug() << "Find message at " << entryCount << " after loading new entries";
|
||||
qDebug() << "Find message at " << entryCount << " after loading new entries " << mEntriesLoading;
|
||||
mPostModelChangedEvents = true;
|
||||
endResetModel();
|
||||
emit tillMessagesLoaded(entryCount);
|
||||
return entryCount;
|
||||
}else
|
||||
newEntries = loadMoreEntries();// continue
|
||||
}
|
||||
mPostModelChangedEvents = true;
|
||||
endResetModel();
|
||||
emit tillMessagesLoaded(newEntries);
|
||||
}else{
|
||||
int entryCount = entry - mList.begin();
|
||||
qDebug() << "Find message at " << entryCount;
|
||||
emit tillMessagesLoaded(entryCount);
|
||||
return entryCount;
|
||||
}
|
||||
qWarning() << "Message has not been found in history";
|
||||
}
|
||||
qWarning() << "Message has not been found in history";
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSharedPointer<ChatMessageModel> ChatRoomModel::getChatMessageModel(const std::shared_ptr<linphone::ChatMessage> message) const {
|
||||
auto entry = std::find_if(mList.begin(), mList.end(), [message](const QSharedPointer<QObject>& entry ){
|
||||
auto chatEventEntry = entry.objectCast<ChatEvent>();
|
||||
return chatEventEntry->mType == ChatRoomModel::EntryType::MessageEntry && chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage() == message;
|
||||
});
|
||||
return entry != mList.end() ? entry->objectCast<ChatMessageModel>(): nullptr;
|
||||
}
|
||||
|
||||
bool ChatRoomModel::isTerminated(const std::shared_ptr<linphone::ChatRoom>& chatRoom){
|
||||
return chatRoom->getState() == linphone::ChatRoom::State::Terminated || chatRoom->getState() == linphone::ChatRoom::State::Deleted;
|
||||
}
|
||||
|
||||
bool ChatRoomModel::exists(const std::shared_ptr<linphone::ChatMessage> message) const{
|
||||
auto entry = std::find_if(mList.begin(), mList.end(), [message](const QSharedPointer<QObject>& entry ){
|
||||
auto chatEventEntry = entry.objectCast<ChatEvent>();
|
||||
return chatEventEntry->mType == ChatRoomModel::EntryType::MessageEntry && chatEventEntry.objectCast<ChatMessageModel>()->getChatMessage() == message;
|
||||
});
|
||||
// if not find, load more entries and find it in new entries.
|
||||
return entry != mList.end();
|
||||
return getChatMessageModel(message) != nullptr;
|
||||
}
|
||||
|
||||
void ChatRoomModel::addBindingCall(){ // If a call is binding to this chat room, we avoid cleaning data (Add=+1, remove=-1)
|
||||
|
|
|
|||
|
|
@ -184,7 +184,10 @@ public:
|
|||
Q_INVOKABLE int loadMoreEntries(); // return new entries count
|
||||
void onCallEnded(std::shared_ptr<linphone::Call> call);
|
||||
void updateNewMessageNotice(const int& count);
|
||||
QSharedPointer<ChatMessageModel> getChatMessageModel(const std::shared_ptr<linphone::ChatMessage> message) const;
|
||||
Q_INVOKABLE int loadTillMessage(ChatMessageModel * message);// Load all entries till message and return its index. -1 if not found.
|
||||
Q_INVOKABLE int loadTillMessageId(const QString& messageId);
|
||||
int loadTillMessage(std::shared_ptr<linphone::ChatMessage> linphoneMessage);
|
||||
static bool isTerminated(const std::shared_ptr<linphone::ChatRoom>& chatRoom);
|
||||
bool exists(const std::shared_ptr<linphone::ChatMessage> message) const;
|
||||
|
||||
|
|
@ -256,9 +259,11 @@ signals:
|
|||
bool isRemoteComposingChanged ();
|
||||
void entriesLoadingChanged(const bool& loading);
|
||||
void moreEntriesLoaded(const int& count);
|
||||
void tillMessagesLoaded(int messageIndex);
|
||||
|
||||
void allEntriesRemoved (QSharedPointer<ChatRoomModel> model);
|
||||
void lastEntryRemoved ();
|
||||
void displayMessageIdRequested(const QString& messageId);
|
||||
|
||||
void messageSent (const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
void messageReceived (const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ void ChatRoomProxyModel::loadMoreEntriesAsync(){
|
|||
void ChatRoomProxyModel::onMoreEntriesLoaded(const int& count){
|
||||
emit moreEntriesLoaded(count);
|
||||
}
|
||||
|
||||
void ChatRoomProxyModel::onTillMessagesLoaded(const int& index){
|
||||
int messageIndex = mapFromSource(static_cast<ChatRoomModel*>(sourceModel())->index(index, 0)).row();
|
||||
emit moreEntriesLoaded(messageIndex);
|
||||
}
|
||||
void ChatRoomProxyModel::loadMoreEntries() {
|
||||
if(mChatRoomModel ) {
|
||||
mChatRoomModel->loadMoreEntries();
|
||||
|
|
@ -283,7 +288,9 @@ void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) {
|
|||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::messageSent, this, &ChatRoomProxyModel::handleMessageSent);
|
||||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::markAsReadEnabledChanged, this, &ChatRoomProxyModel::markAsReadEnabledChanged);
|
||||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::moreEntriesLoaded, this, &ChatRoomProxyModel::onMoreEntriesLoaded);
|
||||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::tillMessagesLoaded, this, &ChatRoomProxyModel::onTillMessagesLoaded);
|
||||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::chatRoomDeleted, this, &ChatRoomProxyModel::chatRoomDeleted);
|
||||
QObject::disconnect(ChatRoomModel, &ChatRoomModel::displayMessageIdRequested, this, &ChatRoomProxyModel::displayMessageIdRequested);
|
||||
if(mIsCall)
|
||||
mChatRoomModel->removeBindingCall();
|
||||
}
|
||||
|
|
@ -300,7 +307,9 @@ void ChatRoomProxyModel::reload (ChatRoomModel *chatRoomModel) {
|
|||
QObject::connect(ChatRoomModel, &ChatRoomModel::messageSent, this, &ChatRoomProxyModel::handleMessageSent);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::markAsReadEnabledChanged, this, &ChatRoomProxyModel::markAsReadEnabledChanged);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::moreEntriesLoaded, this, &ChatRoomProxyModel::onMoreEntriesLoaded);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::tillMessagesLoaded, this, &ChatRoomProxyModel::onTillMessagesLoaded);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::chatRoomDeleted, this, &ChatRoomProxyModel::chatRoomDeleted);
|
||||
QObject::connect(ChatRoomModel, &ChatRoomModel::displayMessageIdRequested, this, &ChatRoomProxyModel::displayMessageIdRequested);
|
||||
mChatRoomModel->initEntries();// This way, we don't load huge chat rooms (that lead to freeze GUI)
|
||||
}
|
||||
setSourceModel(mChatRoomModel.get());
|
||||
|
|
@ -336,9 +345,17 @@ int ChatRoomProxyModel::loadTillMessage(ChatMessageModel * message){
|
|||
return messageIndex;
|
||||
}
|
||||
|
||||
int ChatRoomProxyModel::loadTillMessageId(const QString& messageId){
|
||||
int messageIndex = mChatRoomModel->loadTillMessageId(messageId);
|
||||
if( messageIndex>= 0 ) {
|
||||
messageIndex = mapFromSource(static_cast<ChatRoomModel*>(sourceModel())->index(messageIndex, 0)).row();
|
||||
}
|
||||
qDebug() << "Message index from chat room proxy : " << messageIndex;
|
||||
return messageIndex;
|
||||
}
|
||||
|
||||
ChatRoomModel *ChatRoomProxyModel::getChatRoomModel () const{
|
||||
return mChatRoomModel.get();
|
||||
|
||||
}
|
||||
|
||||
void ChatRoomProxyModel::setChatRoomModel (ChatRoomModel *chatRoomModel){
|
||||
|
|
|
|||
|
|
@ -74,9 +74,11 @@ public:
|
|||
Q_INVOKABLE void resetMessageCount();
|
||||
|
||||
Q_INVOKABLE int loadTillMessage(ChatMessageModel * message);// Load all entries till message and return its index in displayed list (-1 if not found)
|
||||
Q_INVOKABLE int loadTillMessageId(const QString& messageId);
|
||||
|
||||
public slots:
|
||||
void onMoreEntriesLoaded(const int& count);
|
||||
void onTillMessagesLoaded(const int& messageIndex);
|
||||
|
||||
signals:
|
||||
void peerAddressChanged (const QString &peerAddress);
|
||||
|
|
@ -91,6 +93,8 @@ signals:
|
|||
void chatRoomDeleted();
|
||||
|
||||
void moreEntriesLoaded (int n);
|
||||
void tillMessagesLoaded(int messageIndex);
|
||||
void displayMessageIdRequested(const QString& messageId);
|
||||
|
||||
void entryTypeFilterChanged (int type);
|
||||
void filterTextChanged();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ void CoreHandlers::connectTo(CoreListener * listener){
|
|||
connect(listener, &CoreListener::logCollectionUploadProgressIndication, this, &CoreHandlers::onLogCollectionUploadProgressIndication);
|
||||
connect(listener, &CoreListener::messageReceived, this, &CoreHandlers::onMessageReceived);
|
||||
connect(listener, &CoreListener::messagesReceived, this, &CoreHandlers::onMessagesReceived);
|
||||
connect(listener, &CoreListener::newMessageReaction, this, &CoreHandlers::onNewMessageReaction);
|
||||
connect(listener, &CoreListener::notifyPresenceReceivedForUriOrTel, this, &CoreHandlers::onNotifyPresenceReceivedForUriOrTel);
|
||||
connect(listener, &CoreListener::notifyPresenceReceived, this, &CoreHandlers::onNotifyPresenceReceived);
|
||||
connect(listener, &CoreListener::qrcodeFound, this, &CoreHandlers::onQrcodeFound);
|
||||
|
|
@ -318,6 +319,56 @@ void CoreHandlers::onMessagesReceived (
|
|||
}
|
||||
}
|
||||
|
||||
void CoreHandlers::onNewMessageReaction(const std::shared_ptr<linphone::Core> & core, const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction){
|
||||
QList<QPair<shared_ptr<linphone::ChatMessage>, std::shared_ptr<const linphone::ChatMessageReaction> >> reactionsToNotify;
|
||||
CoreManager *coreManager = CoreManager::getInstance();
|
||||
SettingsModel *settingsModel = coreManager->getSettingsModel();
|
||||
const App *app = App::getInstance();
|
||||
QStringList notNotifyReasons;
|
||||
QSettings appSettings;
|
||||
|
||||
appSettings.beginGroup("chatrooms");
|
||||
|
||||
if( !message || CoreManager::getInstance()->getAccountSettingsModel()->findAccount(reaction->getFromAddress()))
|
||||
return;
|
||||
// 1. Do not notify if chat is not activated.
|
||||
if (chatRoom->getCurrentParams()->getEncryptionBackend() == linphone::ChatRoom::EncryptionBackend::None && !settingsModel->getStandardChatEnabled()
|
||||
|| chatRoom->getCurrentParams()->getEncryptionBackend() != linphone::ChatRoom::EncryptionBackend::None && !settingsModel->getSecureChatEnabled())
|
||||
return;
|
||||
|
||||
// 2. Do not notify if the chatroom's notification has been deactivated.
|
||||
appSettings.beginGroup(ChatRoomModel::getChatRoomId(chatRoom));
|
||||
if(!appSettings.value("notifications", true).toBool()){
|
||||
appSettings.endGroup();
|
||||
return;
|
||||
}else{
|
||||
appSettings.endGroup();
|
||||
}
|
||||
// 3. Notify with Notification popup.
|
||||
if (coreManager->getSettingsModel()->getChatNotificationsEnabled()
|
||||
&& (!app->hasFocus() || !Utils::isMe(chatRoom->getLocalAddress())))
|
||||
reactionsToNotify.push_back({message, reaction});
|
||||
else{
|
||||
notNotifyReasons.push_back(
|
||||
"NotifEnabled=" + QString::number(coreManager->getSettingsModel()->getChatNotificationsEnabled())
|
||||
+" focus=" +QString::number(app->hasFocus())
|
||||
+" isMe=" +QString::number(Utils::isMe(chatRoom->getLocalAddress()))
|
||||
);
|
||||
}
|
||||
if( reactionsToNotify.size() > 0)
|
||||
app->getNotifier()->notifyReceivedReactions(reactionsToNotify);
|
||||
else if( notNotifyReasons.size() > 0)
|
||||
qInfo() << "Notification received but was not selected to popup. Reasons : \n" << notNotifyReasons.join("\n");
|
||||
// 3. Notify with sound.
|
||||
if( reactionsToNotify.size() > 0) {
|
||||
if (!coreManager->getSettingsModel()->getChatNotificationsEnabled() || !settingsModel->getChatNotificationSoundEnabled())
|
||||
return;
|
||||
|
||||
if ( !app->hasFocus() || !CoreManager::getInstance()->getTimelineListModel()->getChatRoomModel(chatRoom, false) )
|
||||
core->playLocal(Utils::appStringToCoreString(settingsModel->getChatNotificationSoundPath()));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreHandlers::onNotifyPresenceReceivedForUriOrTel (
|
||||
const shared_ptr<linphone::Core> &,
|
||||
const shared_ptr<linphone::Friend> &,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ signals:
|
|||
void isComposingChanged (const std::shared_ptr<linphone::ChatRoom> &chatRoom);
|
||||
void logsUploadStateChanged (linphone::Core::LogCollectionUploadState state, const std::string &info);
|
||||
void messagesReceived (const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
|
||||
void newMessageReaction(const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction);
|
||||
void presenceReceived (const QString &sipAddress, const std::shared_ptr<const linphone::PresenceModel> &presenceModel);
|
||||
void presenceStatusReceived(std::shared_ptr<linphone::Friend> contact);
|
||||
void registrationStateChanged (const std::shared_ptr<linphone::Account> &account, linphone::RegistrationState state);
|
||||
|
|
@ -82,6 +83,7 @@ public slots:
|
|||
void onLogCollectionUploadProgressIndication (const std::shared_ptr<linphone::Core> &lc,size_t offset,size_t total);
|
||||
void onMessageReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
void onMessagesReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
|
||||
void onNewMessageReaction(const std::shared_ptr<linphone::Core> & core, const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction);
|
||||
void onNotifyPresenceReceivedForUriOrTel (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend,const std::string &uriOrTel,const std::shared_ptr<const linphone::PresenceModel> &presenceModel);
|
||||
void onNotifyPresenceReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend);
|
||||
void onQrcodeFound(const std::shared_ptr<linphone::Core> & core, const std::string & result);
|
||||
|
|
|
|||
|
|
@ -81,6 +81,9 @@ void CoreListener::onMessageReceived (const std::shared_ptr<linphone::Core> &cor
|
|||
void CoreListener::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 messagesReceived (core,room,messages);
|
||||
}
|
||||
void CoreListener::onNewMessageReaction(const std::shared_ptr<linphone::Core> & core, const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction){
|
||||
emit newMessageReaction (core,chatRoom,message, reaction);
|
||||
}
|
||||
void CoreListener::onNotifyPresenceReceivedForUriOrTel (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend,const std::string &uriOrTel,const std::shared_ptr<const linphone::PresenceModel> &presenceModel){
|
||||
emit notifyPresenceReceivedForUriOrTel (core,linphoneFriend,uriOrTel,presenceModel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ public:
|
|||
virtual void onLogCollectionUploadProgressIndication (const std::shared_ptr<linphone::Core> &lc,size_t offset,size_t total) override;
|
||||
virtual void onMessageReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::shared_ptr<linphone::ChatMessage> &message) override;
|
||||
virtual void onMessagesReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) override;
|
||||
virtual void onNewMessageReaction(const std::shared_ptr<linphone::Core> & core, const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction) override;
|
||||
virtual void onNotifyPresenceReceivedForUriOrTel (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend,const std::string &uriOrTel,const std::shared_ptr<const linphone::PresenceModel> &presenceModel) override;
|
||||
virtual void onNotifyPresenceReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend) override;
|
||||
virtual void onQrcodeFound(const std::shared_ptr<linphone::Core> & core, const std::string & result) override;
|
||||
|
|
@ -78,6 +79,7 @@ signals:
|
|||
void logCollectionUploadProgressIndication (const std::shared_ptr<linphone::Core> &lc,size_t offset,size_t total);
|
||||
void messageReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
void messagesReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::ChatRoom> &room,const std::list<std::shared_ptr<linphone::ChatMessage>> &messages);
|
||||
void newMessageReaction(const std::shared_ptr<linphone::Core> & core, const std::shared_ptr<linphone::ChatRoom> & chatRoom, const std::shared_ptr<linphone::ChatMessage> & message, const std::shared_ptr<const linphone::ChatMessageReaction> & reaction);
|
||||
void notifyPresenceReceivedForUriOrTel (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend,const std::string &uriOrTel,const std::shared_ptr<const linphone::PresenceModel> &presenceModel);
|
||||
void notifyPresenceReceived (const std::shared_ptr<linphone::Core> &core,const std::shared_ptr<linphone::Friend> &linphoneFriend);
|
||||
void qrcodeFound(const std::shared_ptr<linphone::Core> & core, const std::string & result);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "app/App.hpp"
|
||||
#include "components/call/CallModel.hpp"
|
||||
#include "components/chat-events/ChatMessageModel.hpp"
|
||||
#include "components/core/CoreManager.hpp"
|
||||
#include "components/timeline/TimelineModel.hpp"
|
||||
#include "components/timeline/TimelineListModel.hpp"
|
||||
|
|
@ -312,6 +313,49 @@ void Notifier::notifyReceivedMessages (const list<shared_ptr<linphone::ChatMessa
|
|||
}
|
||||
}
|
||||
|
||||
void Notifier::notifyReceivedReactions(const QList<QPair<std::shared_ptr<linphone::ChatMessage>, std::shared_ptr<const linphone::ChatMessageReaction>>> &reactions) {
|
||||
QVariantMap map;
|
||||
QString txt;
|
||||
|
||||
if( reactions.size() > 0){
|
||||
ChatMessageModel *redirection = nullptr;
|
||||
QPair<shared_ptr<linphone::ChatMessage>,std::shared_ptr<const linphone::ChatMessageReaction>> reaction = reactions.front();
|
||||
shared_ptr<linphone::ChatMessage> message = reaction.first;
|
||||
shared_ptr<linphone::ChatRoom> chatRoom(message->getChatRoom());
|
||||
auto timelineModel = CoreManager::getInstance()->getTimelineListModel()->getTimeline(chatRoom, true);
|
||||
map["messageId"] = Utils::coreStringToAppString(message->getMessageId());
|
||||
if( reactions.size() == 1){
|
||||
QString messageTxt;
|
||||
auto fileContent = message->getFileTransferInformation();
|
||||
if(!fileContent ){
|
||||
foreach(auto content, message->getContents()){
|
||||
if(content->isText())
|
||||
messageTxt += content->getUtf8Text().c_str();
|
||||
}
|
||||
}else if( fileContent->isVoiceRecording())
|
||||
messageTxt += "Voice message";
|
||||
else
|
||||
messageTxt += "File";
|
||||
if(messageTxt.isEmpty() && message->hasConferenceInvitationContent())
|
||||
messageTxt += "Conference invitation";
|
||||
txt = QString("Has reacted by %2 to: %3").arg(Utils::coreStringToAppString(reaction.second->getBody())).arg(messageTxt);
|
||||
|
||||
}else
|
||||
txt = "New message reactions received";
|
||||
map["message"] = txt;
|
||||
|
||||
map["timelineModel"].setValue(timelineModel.get());
|
||||
if( reactions.size() == 1) {// Display only sender on mono message.
|
||||
map["peerAddress"] = Utils::coreStringToAppString(reaction.second->getFromAddress()->asStringUriOnly());
|
||||
map["fullPeerAddress"] = Utils::coreStringToAppString(reaction.second->getFromAddress()->asString());
|
||||
}
|
||||
map["localAddress"] = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asStringUriOnly());
|
||||
map["fullLocalAddress"] = Utils::coreStringToAppString(chatRoom->getLocalAddress()->asString());
|
||||
map["window"].setValue(App::getInstance()->getMainWindow());
|
||||
CREATE_NOTIFICATION(Notifier::ReceivedMessage, map)
|
||||
}
|
||||
}
|
||||
|
||||
void Notifier::notifyReceivedFileMessage (const shared_ptr<linphone::ChatMessage> &message, const shared_ptr<linphone::Content> &content) {
|
||||
QVariantMap map;
|
||||
shared_ptr<linphone::ChatRoom> chatRoom(message->getChatRoom());
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public:
|
|||
};
|
||||
|
||||
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 std::shared_ptr<linphone::ChatMessage> &message, const std::shared_ptr<linphone::Content> &content);
|
||||
void notifyReceivedCall (const std::shared_ptr<linphone::Call> &call);
|
||||
void notifyNewVersionAvailable (const QString &version, const QString &url);
|
||||
|
|
|
|||
|
|
@ -309,6 +309,9 @@ class ColorListModel : public ProxyListModel {
|
|||
ADD_COLOR_WITH_ALPHA("i", 30, "")
|
||||
ADD_COLOR_WITH_ALPHA("j", 50, "")
|
||||
ADD_COLOR_WITH_ALPHA("j", 90, "")
|
||||
ADD_COLOR_WITH_ALPHA("l", 10, "")
|
||||
ADD_COLOR_WITH_ALPHA("l", 20, "")
|
||||
ADD_COLOR_WITH_ALPHA("l", 30, "")
|
||||
ADD_COLOR_WITH_ALPHA("l", 50, "")
|
||||
ADD_COLOR_WITH_ALPHA("l", 80, "")
|
||||
ADD_COLOR_WITH_ALPHA("q", 50, "")
|
||||
|
|
|
|||
|
|
@ -30,8 +30,7 @@
|
|||
|
||||
#include <linphone++/chat_room.hh>
|
||||
|
||||
#include "../contact/ContactModel.hpp"
|
||||
|
||||
class ChatMessageModel;
|
||||
class ChatRoomModel;
|
||||
class ChatRoomListener;
|
||||
class TimelineListModel;
|
||||
|
|
|
|||
|
|
@ -14,45 +14,53 @@ Controls.TabButton {
|
|||
|
||||
property int iconSize: TabButtonStyle.icon.size
|
||||
property string iconName
|
||||
property alias textFont: textItem.font
|
||||
|
||||
readonly property bool _isSelected: parent.parent.currentItem === button
|
||||
readonly property bool isSelected: parent.parent.currentItem === button
|
||||
|
||||
property bool displaySelector: false
|
||||
property bool stretchContent: true
|
||||
property QtObject style: TabButtonStyle.menu
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function _getBackgroundColor () {
|
||||
if (_isSelected) {
|
||||
return TabButtonStyle.backgroundColor.selected.color
|
||||
if(!button.style)
|
||||
return ''
|
||||
if (isSelected) {
|
||||
return button.style.backgroundColor.selected.color
|
||||
}
|
||||
|
||||
return button.enabled
|
||||
? (
|
||||
button.down
|
||||
? TabButtonStyle.backgroundColor.pressed.color
|
||||
? button.style.backgroundColor.pressed.color
|
||||
: (
|
||||
button.hovered
|
||||
? TabButtonStyle.backgroundColor.hovered.color
|
||||
: TabButtonStyle.backgroundColor.normal.color
|
||||
? button.style.backgroundColor.hovered.color
|
||||
: button.style.backgroundColor.normal.color
|
||||
)
|
||||
)
|
||||
: TabButtonStyle.backgroundColor.disabled.color
|
||||
: button.style.backgroundColor.disabled.color
|
||||
}
|
||||
|
||||
function _getTextColor () {
|
||||
if (_isSelected) {
|
||||
return TabButtonStyle.text.color.selected.color
|
||||
if(!button.style)
|
||||
return ''
|
||||
if (isSelected) {
|
||||
return button.style.text.color.selected.color
|
||||
}
|
||||
|
||||
return button.enabled
|
||||
? (
|
||||
button.down
|
||||
? TabButtonStyle.text.color.pressed.color
|
||||
? button.style.text.color.pressed.color
|
||||
: (
|
||||
button.hovered
|
||||
? TabButtonStyle.text.color.hovered.color
|
||||
: TabButtonStyle.text.color.normal.color
|
||||
? button.style.text.color.hovered.color
|
||||
: button.style.text.color.normal.color
|
||||
)
|
||||
)
|
||||
: TabButtonStyle.text.color.disabled.color
|
||||
: button.style.text.color.disabled.color
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -60,22 +68,35 @@ Controls.TabButton {
|
|||
background: Rectangle {
|
||||
color: _getBackgroundColor()
|
||||
implicitHeight: TabButtonStyle.text.height
|
||||
Rectangle{
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 2
|
||||
visible: button.displaySelector && button.isSelected
|
||||
color: button.style ? button.style.selector.color : ''
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
height: button.height
|
||||
width: button.width
|
||||
spacing: TabButtonStyle.spacing
|
||||
|
||||
Item{
|
||||
Layout.fillWidth: visible
|
||||
Layout.fillHeight: true
|
||||
visible: !button.stretchContent
|
||||
}
|
||||
Icon {
|
||||
id: icon
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: TabButtonStyle.text.leftPadding
|
||||
Layout.leftMargin: visible ? TabButtonStyle.text.leftPadding : 0
|
||||
overwriteColor: textItem.color
|
||||
|
||||
icon: button.iconName
|
||||
iconSize: button.iconSize
|
||||
iconSize: visible ? button.iconSize : 0
|
||||
visible: button.iconName
|
||||
}
|
||||
|
||||
Text {
|
||||
|
|
@ -93,11 +114,18 @@ Controls.TabButton {
|
|||
elide: Text.ElideRight
|
||||
|
||||
height: parent.height
|
||||
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
rightPadding: TabButtonStyle.text.rightPadding
|
||||
text: button.text
|
||||
textFormat: Text.RichText
|
||||
}
|
||||
Item{
|
||||
Layout.fillWidth: visible
|
||||
Layout.fillHeight: true
|
||||
visible: !button.stretchContent
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,17 +7,7 @@ import ColorsList 1.0
|
|||
// =============================================================================
|
||||
|
||||
QtObject {
|
||||
property string sectionName: 'TabButton'
|
||||
property int spacing: 8
|
||||
|
||||
property QtObject backgroundColor: QtObject {
|
||||
property var disabled: ColorsList.add(sectionName+'_bg_d', 'i30')
|
||||
property var hovered: ColorsList.add(sectionName+'_bg_h', 'b')
|
||||
property var normal: ColorsList.add(sectionName+'_bg_n', 'i')
|
||||
property var pressed: ColorsList.add(sectionName+'_bg_p', 'm')
|
||||
property var selected: ColorsList.add(sectionName+'_bg_c', 'k')
|
||||
}
|
||||
|
||||
property QtObject icon: QtObject {
|
||||
property int size: 20
|
||||
property string sipAccountsIcon: 'settings_sip_accounts_custom'
|
||||
|
|
@ -27,19 +17,58 @@ QtObject {
|
|||
property string networkIcon: 'settings_network_custom'
|
||||
property string advancedIcon: 'settings_advanced_custom'
|
||||
}
|
||||
|
||||
property QtObject text: QtObject {
|
||||
property int pointSize: Units.dp * 9
|
||||
property int height: 40
|
||||
property int leftPadding: 10
|
||||
property int rightPadding: 10
|
||||
}
|
||||
|
||||
property QtObject menu: QtObject{
|
||||
property string sectionName: 'TabButtonMenu'
|
||||
|
||||
property QtObject color: QtObject {
|
||||
property var disabled: ColorsList.add(sectionName+'_text_d', 'q')
|
||||
property var hovered: ColorsList.add(sectionName+'_text_h', 'q')
|
||||
property var normal: ColorsList.add(sectionName+'_text_n', 'q')
|
||||
property var pressed: ColorsList.add(sectionName+'_text_p', 'q')
|
||||
property var selected: ColorsList.add(sectionName+'_text_c', 'i')
|
||||
property QtObject backgroundColor: QtObject {
|
||||
property var disabled: ColorsList.add(menu.sectionName+'_bg_d', 'i30')
|
||||
property var hovered: ColorsList.add(menu.sectionName+'_bg_h', 'b')
|
||||
property var normal: ColorsList.add(menu.sectionName+'_bg_n', 'i')
|
||||
property var pressed: ColorsList.add(menu.sectionName+'_bg_p', 'm')
|
||||
property var selected: ColorsList.add(menu.sectionName+'_bg_c', 'k')
|
||||
}
|
||||
|
||||
property QtObject text: QtObject {
|
||||
|
||||
property QtObject color: QtObject {
|
||||
property var disabled: ColorsList.add(menu.sectionName+'_text_d', 'q')
|
||||
property var hovered: ColorsList.add(menu.sectionName+'_text_h', 'q')
|
||||
property var normal: ColorsList.add(menu.sectionName+'_text_n', 'q')
|
||||
property var pressed: ColorsList.add(menu.sectionName+'_text_p', 'q')
|
||||
property var selected: ColorsList.add(menu.sectionName+'_text_c', 'i')
|
||||
}
|
||||
}
|
||||
property var selector: ColorsList.add(menu.sectionName+'_selector', 'i')
|
||||
}
|
||||
property QtObject popup: QtObject{
|
||||
property string sectionName: 'TabButtonPopup'
|
||||
|
||||
property QtObject backgroundColor: QtObject {
|
||||
property var disabled: ColorsList.add(popup.sectionName+'_bg_d', 'l10')
|
||||
property var hovered: ColorsList.add(popup.sectionName+'_bg_h', 'l20')
|
||||
property var normal: ColorsList.add(popup.sectionName+'_bg_n', 'a')
|
||||
property var pressed: ColorsList.add(popup.sectionName+'_bg_p', 'l30')
|
||||
property var selected: ColorsList.add(popup.sectionName+'_bg_c', 'a')
|
||||
}
|
||||
|
||||
|
||||
property QtObject text: QtObject {
|
||||
|
||||
property QtObject color: QtObject {
|
||||
property var disabled: ColorsList.add(popup.sectionName+'_text_d', 'g')
|
||||
property var hovered: ColorsList.add(popup.sectionName+'_text_h', 'g')
|
||||
property var normal: ColorsList.add(popup.sectionName+'_text_n', 'g')
|
||||
property var pressed: ColorsList.add(popup.sectionName+'_text_p', 'g')
|
||||
property var selected: ColorsList.add(popup.sectionName+'_text_c', 'i')
|
||||
}
|
||||
}
|
||||
property var selector: ColorsList.add(popup.sectionName+'_selector', 'i')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ function handleFilesDropped (files) {
|
|||
|
||||
function handleMoreEntriesLoaded (n) {
|
||||
chat.positionViewAtIndex(n - 1, QtQuick.ListView.Beginning)
|
||||
//moveToEvent.indexToMove = n > 0 ? n - 1 : 0
|
||||
}
|
||||
|
||||
function handleMovementEnded () {
|
||||
|
|
|
|||
|
|
@ -36,22 +36,46 @@ Rectangle {
|
|||
|
||||
color: ChatStyle.colorModel.color
|
||||
clip: true
|
||||
|
||||
Item{// Let some time to have a better cell sizes
|
||||
id: moveToEvent
|
||||
property int indexToMove: -1
|
||||
property bool toReposition: indexToMove>=0 && !container.tryingToLoadMoreEntries
|
||||
|
||||
function reposition(){
|
||||
chat.positionViewAtIndex(indexToMove, ListView.Center)
|
||||
repositionerDelay.indexToMove = indexToMove
|
||||
indexToMove = -1
|
||||
repositionerDelay.restart()
|
||||
}
|
||||
onToRepositionChanged: if(toReposition){
|
||||
console.debug('Moving to ' + indexToMove)
|
||||
Qt.callLater(reposition);
|
||||
}
|
||||
}
|
||||
Timer{// Let some time to have a better cell sizes
|
||||
id: repositionerDelay
|
||||
property int indexToMove
|
||||
property int indexToMove: -1
|
||||
interval: 100
|
||||
onTriggered: chat.positionViewAtIndex(indexToMove, ListView.Center)
|
||||
onTriggered: {
|
||||
chat.positionViewAtIndex(indexToMove, ListView.Center)
|
||||
}
|
||||
}
|
||||
|
||||
function positionViewAtIndex(index){
|
||||
chat.bindToEnd = false
|
||||
chat.positionViewAtIndex(index, ListView.Center)
|
||||
repositionerDelay.indexToMove = index
|
||||
repositionerDelay.restart()
|
||||
if(index>=0) {
|
||||
chat.bindToEnd = false
|
||||
chat.positionViewAtIndex(index, ListView.Center)
|
||||
moveToEvent.indexToMove = index
|
||||
}
|
||||
}
|
||||
|
||||
function goToMessage(message){
|
||||
positionViewAtIndex(container.proxyModel.loadTillMessage(message))
|
||||
}
|
||||
function goToMessageId(messageId){
|
||||
positionViewAtIndex(container.proxyModel.loadTillMessageId(messageId))
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
|
@ -61,10 +85,11 @@ Rectangle {
|
|||
id: chat
|
||||
// -----------------------------------------------------------------------
|
||||
property bool displaying: false
|
||||
property bool entriesLoading: container.proxyModel.chatRoomModel && container.proxyModel.chatRoomModel.entriesLoading
|
||||
onEntriesLoadingChanged: console.log("entriesLoading="+entriesLoading)
|
||||
property bool loadingEntries: (container.proxyModel.chatRoomModel && container.proxyModel.chatRoomModel.entriesLoading) || displaying
|
||||
property bool tryToLoadMoreEntries: loadingEntries || remainingLoadersCount>0
|
||||
property bool isMoving : false // replace moving read-only property to allow using movement signals.
|
||||
|
||||
// Load optimizations
|
||||
property int remainingLoadersCount: 0
|
||||
property int visibleItemsEstimation: chat.height / (2 * textMetrics.height) // Title + body
|
||||
|
|
@ -74,8 +99,9 @@ Rectangle {
|
|||
signal refreshContents()
|
||||
|
||||
onLoadingEntriesChanged: {
|
||||
if( loadingEntries && !displaying)
|
||||
if( loadingEntries && !displaying && container.proxyModel.chatRoomModel.entriesLoading) {
|
||||
displaying = true
|
||||
}
|
||||
}
|
||||
onBindToEndChanged: if( bindToEnd){
|
||||
markAsReadTimer.start()
|
||||
|
|
@ -116,7 +142,7 @@ Rectangle {
|
|||
Component.onCompleted: {
|
||||
Logic.initView()
|
||||
refreshContentsTimer.start()
|
||||
console.debug("Chat loading with "+chat.visibleItemsEstimation+" visible items. "+chat.count)
|
||||
console.debug("Chat loading with "+chat.visibleItemsEstimation+" visible items. Count="+chat.count)
|
||||
if(chat.visibleItemsEstimation >= chat.count)
|
||||
Qt.callLater(container.proxyModel.loadMoreEntriesAsync)
|
||||
}
|
||||
|
|
@ -137,6 +163,11 @@ Rectangle {
|
|||
Logic.handleMoreEntriesLoaded(n)// move view to n - 1 item
|
||||
chat.displaying = false
|
||||
}
|
||||
onTillMessagesLoaded: positionViewAtIndex(messageIndex)
|
||||
onDisplayMessageIdRequested: {
|
||||
console.log("Display Message requested "+messageId)
|
||||
container.goToMessageId(messageId)
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
|
@ -303,6 +334,7 @@ Rectangle {
|
|||
onConferenceIcsCopied: container.noticeBannerText = qsTr('conferencesCopiedICS')
|
||||
onAddContactClicked: container.addContactClicked(contactAddress)
|
||||
onViewContactClicked: container.viewContactClicked(contactAddress)
|
||||
onReactionsClicked: chatReactionsDetails.show(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -465,5 +497,10 @@ Rectangle {
|
|||
}// ColumnLayout
|
||||
}// Bottom background
|
||||
}
|
||||
ChatReactionsDetails {
|
||||
id: chatReactionsDetails
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,33 @@ Item {
|
|||
Menu {
|
||||
id: messageMenu
|
||||
menuStyle : MenuStyle.aux
|
||||
MenuItem {
|
||||
id: reactionBar
|
||||
property font customFont : SettingsModel.emojiFont
|
||||
menuItemStyle : MenuItemStyle.aux
|
||||
contentItem: RowLayout{
|
||||
Layout.fillWidth: true
|
||||
spacing:0
|
||||
Repeater{
|
||||
model: ['❤️','👍','😂','😮','😢']
|
||||
delegate: Text{
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: modelData
|
||||
font.family: reactionBar.customFont.family
|
||||
font.pointSize: Units.dp * reactionBar.customFont.pointSize * 2
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
chatMessageModel.sendChatReaction(modelData)
|
||||
messageMenu.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
//: 'Copy all' : Text menu to copy all message text into clipboard
|
||||
text: (container.lastTextSelected == '' ? qsTr('menuCopyAll')
|
||||
|
|
|
|||
121
linphone-app/ui/modules/Linphone/Chat/ChatReactionsDetails.qml
Normal file
121
linphone-app/ui/modules/Linphone/Chat/ChatReactionsDetails.qml
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import QtQuick 2.7
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import Common 1.0
|
||||
import Common.Styles 1.0
|
||||
import Linphone 1.0
|
||||
import Linphone.Styles 1.0
|
||||
import Utils 1.0
|
||||
import UtilsCpp 1.0
|
||||
import LinphoneEnums 1.0
|
||||
import Units 1.0
|
||||
import ColorsList 1.0
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Rectangle{
|
||||
id: mainItem
|
||||
property string sectionName: 'ChatReactions'
|
||||
property font emojiFont : SettingsModel.emojiFont
|
||||
property font textFont : SettingsModel.textMessageFont
|
||||
property alias chatMessageModel: chatReactionsList.chatMessageModel
|
||||
|
||||
|
||||
function show(message){
|
||||
chatReactionsList.setChatMessageModel(message, ChatReactionListModel.REACTIONS)
|
||||
visible = true
|
||||
}
|
||||
|
||||
color: ChatReactionsDetailsStyle.backgroundColorModel.color
|
||||
onVisibleChanged: if(visible){
|
||||
tabBar.currentIndex = 0
|
||||
}
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked: mainItem.visible = false
|
||||
}
|
||||
Rectangle{
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: parent.height / 2
|
||||
color: ChatReactionsDetailsStyle.stickerColorModel.color
|
||||
|
||||
ColumnLayout{
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
TabBar {
|
||||
Layout.fillWidth: true
|
||||
id: tabBar
|
||||
TabButton {
|
||||
Layout.fillWidth: true
|
||||
//: "%1<br>reactions" : count of all chat reactions with a jump line between count and text.
|
||||
text: UtilsCpp.encodeTextToQmlRichFormat(qsTr('reactionsCount', '', chatReactionsList.reactionCount).arg(chatReactionsList.reactionCount), {noLink:1}).toUpperCase()
|
||||
// noLink=1 to avoid <br> convertion
|
||||
textFont: mainItem.textFont
|
||||
onIsSelectedChanged: if(isSelected) chatReactionsList.filter = ''
|
||||
displaySelector: true
|
||||
stretchContent: false
|
||||
style: TabButtonStyle.popup
|
||||
}
|
||||
Repeater{
|
||||
model: ['❤️','👍','😂','😮','😢']
|
||||
delegate: TabButton {
|
||||
width: visible ? undefined : 0
|
||||
property int reactionCount: 0
|
||||
visible: reactionCount > 0
|
||||
text: UtilsCpp.encodeTextToQmlRichFormat(modelData + ' '+reactionCount)
|
||||
textFont.family: mainItem.textFont.family
|
||||
textFont.pointSize: ChatReactionsDetailsStyle.tabBar.pointSize
|
||||
|
||||
onIsSelectedChanged: if(isSelected) chatReactionsList.filter = modelData
|
||||
displaySelector: true
|
||||
stretchContent: false
|
||||
style: TabButtonStyle.popup
|
||||
|
||||
Connections{
|
||||
target: chatReactionsList
|
||||
onChatMessageModelChanged: reactionCount = chatReactionsList.getChatReactionCount(modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle{
|
||||
id: separator
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 2
|
||||
color: ChatReactionsDetailsStyle.separatorColorModel.color
|
||||
}
|
||||
Item{
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 10
|
||||
ScrollableListView{
|
||||
id: listView
|
||||
anchors.fill: parent
|
||||
model: ChatReactionProxyModel{
|
||||
id: chatReactionsList
|
||||
groupBy: ChatReactionListModel.REACTIONS
|
||||
}
|
||||
delegate: RowLayout{
|
||||
width: listView.width
|
||||
Contact {
|
||||
Layout.fillWidth: true
|
||||
showSubtitle: false
|
||||
property var sipObserver: SipAddressesModel.getSipAddressObserver($modelData.reaction.fromAddress, $modelData.reaction.fromAddress)
|
||||
entry: sipObserver
|
||||
Component.onDestruction: sipObserver=null// Need to set it to null because of not calling destructor if not.
|
||||
}
|
||||
Text{
|
||||
Layout.rightMargin: 20
|
||||
text: $modelData.reaction.body
|
||||
font.family: mainItem.emojiFont.family
|
||||
font.pointSize: mainItem.emojiFont.pointSize * 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import UtilsCpp 1.0
|
|||
// =============================================================================
|
||||
|
||||
RowLayout {
|
||||
id:mainRow
|
||||
id: mainRow
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ RowLayout {
|
|||
signal conferenceIcsCopied()
|
||||
signal addContactClicked(string contactAddress)
|
||||
signal viewContactClicked(string contactAddress)
|
||||
signal reactionsClicked(ChatMessageModel message)
|
||||
|
||||
implicitHeight: message.height
|
||||
spacing: 0
|
||||
|
|
@ -70,6 +71,7 @@ RowLayout {
|
|||
onConferenceIcsCopied: mainRow.conferenceIcsCopied()
|
||||
onAddContactClicked: mainRow.addContactClicked(contactAddress)
|
||||
onViewContactClicked: mainRow.viewContactClicked(contactAddress)
|
||||
onReactionsClicked: mainRow.reactionsClicked(message)
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.rightMargin: 10
|
||||
|
|
|
|||
|
|
@ -42,10 +42,11 @@ Item {
|
|||
signal conferenceIcsCopied()
|
||||
signal addContactClicked(string contactAddress)
|
||||
signal viewContactClicked(string contactAddress)
|
||||
signal reactionsClicked(ChatMessageModel message);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
property string lastTextSelected
|
||||
implicitHeight: (deliveryLayout.visible? deliveryLayout.height : 0) +(ephemeralTimerRow.visible? 16 : 0) + chatContent.height
|
||||
implicitHeight: (deliveryLayout.visible? deliveryLayout.height : 0) +(ephemeralTimerRow.visible? 16 : 0) + chatContent.height + reactionItem.height-10
|
||||
Rectangle {
|
||||
id: rectangle
|
||||
property int availableWidth: parent.width
|
||||
|
|
@ -55,7 +56,7 @@ Item {
|
|||
anchors.left: !$chatEntry.isOutgoing ? parent.left : undefined
|
||||
anchors.right: $chatEntry.isOutgoing ? parent.right : undefined
|
||||
|
||||
height: parent.height - (deliveryLayout.visible? deliveryLayout.height : 0)
|
||||
height: parent.height - (deliveryLayout.visible? deliveryLayout.height : 0) - (reactionItem.height-10)
|
||||
radius: ChatStyle.entry.message.radius
|
||||
clip: false
|
||||
color: colorModel.color
|
||||
|
|
@ -143,6 +144,71 @@ Item {
|
|||
|
||||
chatMessageModel: $chatEntry
|
||||
}
|
||||
Rectangle{
|
||||
id: reactionItem
|
||||
anchors.top: rectangle.bottom
|
||||
anchors.left: !$chatEntry.isOutgoing ? rectangle.left : undefined
|
||||
anchors.right: $chatEntry.isOutgoing ? rectangle.right : undefined
|
||||
anchors.topMargin: -10
|
||||
anchors.leftMargin: 5
|
||||
anchors.rightMargin: 5
|
||||
height: visible ? reactionList.height +10 : 0
|
||||
width: visible ? reactionLayout.width + 10 : 0
|
||||
color: rectangle.color
|
||||
radius: rectangle.radius
|
||||
visible: reactionList.count > 0
|
||||
property font customFont : SettingsModel.textMessageFont
|
||||
property font customEmojiFont : SettingsModel.emojiFont
|
||||
|
||||
RowLayout{
|
||||
id: reactionLayout
|
||||
anchors.centerIn: parent
|
||||
ListView{
|
||||
id: reactionList
|
||||
Layout.preferredWidth: contentItem.childrenRect.width
|
||||
Layout.preferredHeight: reactionMetrics.height
|
||||
TextMetrics{
|
||||
id: reactionMetrics
|
||||
text: '😮'
|
||||
font.family: reactionItem.customEmojiFont.family
|
||||
font.pointSize: Units.dp * reactionItem.customEmojiFont.pointSize * 2
|
||||
}
|
||||
orientation: ListView.Horizontal
|
||||
model: ChatReactionProxyModel{
|
||||
id: chatReactionProxyModel
|
||||
chatMessageModel: $chatEntry
|
||||
}
|
||||
spacing: 10
|
||||
delegate:
|
||||
Text{
|
||||
width: reactionMetrics.width
|
||||
height: reactionList.height
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: reactionItem.customEmojiFont.family
|
||||
font.pointSize: Units.dp * reactionItem.customEmojiFont.pointSize * 2
|
||||
text: $modelData.body || ''
|
||||
}
|
||||
}
|
||||
|
||||
Text{
|
||||
Layout.preferredWidth: contentWidth
|
||||
Layout.preferredHeight: reactionList.height
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
font.family: reactionItem.customFont.family
|
||||
font.pointSize: Units.dp * reactionItem.customFont.pointSize
|
||||
visible: chatReactionProxyModel.count != chatReactionProxyModel.reactionCount
|
||||
|
||||
text: chatReactionProxyModel.reactionCount
|
||||
}
|
||||
}
|
||||
MouseArea{
|
||||
anchors.fill: parent
|
||||
onClicked: container.reactionsClicked($chatEntry)
|
||||
}
|
||||
}
|
||||
|
||||
ActionButton {
|
||||
id: menuButton
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ Item {
|
|||
signal conferenceIcsCopied()
|
||||
signal addContactClicked(string contactAddress)
|
||||
signal viewContactClicked(string contactAddress)
|
||||
signal reactionsClicked(ChatMessageModel message)
|
||||
|
||||
implicitHeight: message.height
|
||||
RowLayout{
|
||||
|
|
@ -43,6 +44,7 @@ Item {
|
|||
onConferenceIcsCopied: mainItem.conferenceIcsCopied()
|
||||
onAddContactClicked: mainItem.addContactClicked(contactAddress)
|
||||
onViewContactClicked: mainItem.viewContactClicked(contactAddress)
|
||||
onReactionsClicked: mainItem.reactionsClicked(message)
|
||||
|
||||
backgroundColorModel: ChatStyle.entry.message.outgoing.backgroundColor
|
||||
Layout.fillWidth: true
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ Notification {
|
|||
readonly property string localAddress: notificationData && notificationData.localAddress || ''
|
||||
readonly property string fullPeerAddress: notificationData && notificationData.fullPeerAddress || ''
|
||||
readonly property string fullLocalAddress: notificationData && notificationData.fullLocalAddress || ''
|
||||
readonly property string messageId: notificationData && notificationData.messageId || ''
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -99,10 +100,10 @@ Notification {
|
|||
AccountSettingsModel.setDefaultAccountFromSipAddress(notification.localAddress)
|
||||
var chatroom = notification.timelineModel.getChatRoomModel()
|
||||
console.debug("Load conversation from notification: "+chatroom)
|
||||
//notification.notificationData.window.setView('Conversation', {
|
||||
// chatRoomModel: chatroom
|
||||
// })
|
||||
notification.timelineModel.selected = true
|
||||
if(!notification.timelineModel.selected)// Check to avoid reloading
|
||||
notification.timelineModel.selected = true
|
||||
if(chatroom && notification.messageId)
|
||||
chatroom.displayMessageIdRequested(notification.messageId)
|
||||
App.smartShowWindow(notification.notificationData.window)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
pragma Singleton
|
||||
import QtQml 2.2
|
||||
|
||||
import Units 1.0
|
||||
import ColorsList 1.0
|
||||
|
||||
// =============================================================================
|
||||
|
||||
QtObject {
|
||||
property string sectionName : 'ChatReactionsDetails'
|
||||
property var backgroundColorModel: ColorsList.add(sectionName+'_bg', 'l50')
|
||||
property var stickerColorModel: ColorsList.add(sectionName+'_sticker_bg', 'k')
|
||||
property var separatorColorModel: ColorsList.add(sectionName+'_separator', 'f')
|
||||
|
||||
property QtObject tabBar: QtObject{
|
||||
property int pointSize: Units.dp * 11
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ singleton ChatEmojisStyle 1.0 Chat/ChatEmojisStyle.qml
|
|||
singleton ChatFilePreviewStyle 1.0 Chat/ChatFilePreviewStyle.qml
|
||||
singleton ChatCalendarMessageStyle 1.0 Chat/ChatCalendarMessageStyle.qml
|
||||
singleton ChatForwardMessageStyle 1.0 Chat/ChatForwardMessageStyle.qml
|
||||
singleton ChatReactionsDetailsStyle 1.0 Chat/ChatReactionsDetailsStyle.qml
|
||||
singleton ChatReplyMessageStyle 1.0 Chat/ChatReplyMessageStyle.qml
|
||||
|
||||
singleton CallControlsStyle 1.0 Calls/CallControlsStyle.qml
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ ChatEmojis 1.0 Chat/ChatEmojis.qml
|
|||
ChatFullContent 1.0 Chat/ChatFullContent.qml
|
||||
ChatMessagePreview 1.0 Chat/ChatMessagePreview.qml
|
||||
ChatForwardMessage 1.0 Chat/ChatForwardMessage.qml
|
||||
ChatReactionsDetails 1.0 Chat/ChatReactionsDetails.qml
|
||||
ChatReplyMessage 1.0 Chat/ChatReplyMessage.qml
|
||||
ChatReplyPreview 1.0 Chat/ChatReplyPreview.qml
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ ApplicationWindow {
|
|||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: TabButtonStyle.text.height
|
||||
|
||||
color: TabButtonStyle.backgroundColor.normal.color
|
||||
color: TabButtonStyle.menu.backgroundColor.normal.color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
@ -134,7 +134,7 @@ ApplicationWindow {
|
|||
Rectangle{
|
||||
id: hideBar
|
||||
anchors.fill: parent
|
||||
color: TabButtonStyle.backgroundColor.normal.color
|
||||
color: TabButtonStyle.menu.backgroundColor.normal.color
|
||||
visible: logViewer.active
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b4e83802c9d24ec019edcf14d90834f304f7c44e
|
||||
Subproject commit 0063e1fb69a31662edb86e73f537b3eb854c7cfe
|
||||
Loading…
Add table
Reference in a new issue