Feature/group chat infos
This commit is contained in:
parent
4ef65219ad
commit
ff78a5abf1
15 changed files with 732 additions and 136 deletions
|
|
@ -67,6 +67,7 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
|
||||||
mTitle = Utils::coreStringToAppString(chatRoom->getSubject());
|
mTitle = Utils::coreStringToAppString(chatRoom->getSubject());
|
||||||
mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject());
|
mAvatarUri = Utils::coreStringToAppString(chatRoom->getSubject());
|
||||||
mIsGroupChat = true;
|
mIsGroupChat = true;
|
||||||
|
mMeAdmin = chatRoom->getMe() && chatRoom->getMe()->isAdmin();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mUnreadMessagesCount = chatRoom->getUnreadMessagesCount();
|
mUnreadMessagesCount = chatRoom->getUnreadMessagesCount();
|
||||||
|
|
@ -102,6 +103,7 @@ ChatCore::ChatCore(const std::shared_ptr<linphone::ChatRoom> &chatRoom) : QObjec
|
||||||
connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage);
|
connect(this, &ChatCore::eventRemoved, this, &ChatCore::lUpdateLastMessage);
|
||||||
mEphemeralEnabled = chatRoom->ephemeralEnabled();
|
mEphemeralEnabled = chatRoom->ephemeralEnabled();
|
||||||
mIsMuted = chatRoom->getMuted();
|
mIsMuted = chatRoom->getMuted();
|
||||||
|
mParticipants = buildParticipants(chatRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatCore::~ChatCore() {
|
ChatCore::~ChatCore() {
|
||||||
|
|
@ -155,18 +157,23 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
mChatModelConnection->makeConnectToModel(&ChatModel::chatMessageReceived, // TODO onNewEvent?
|
// Events (excluding messages)
|
||||||
|
mChatModelConnection->makeConnectToModel(&ChatModel::newEvent,
|
||||||
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
||||||
if (mChatModel->getMonitor() != chatRoom) return;
|
if (mChatModel->getMonitor() != chatRoom) return;
|
||||||
qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
|
qDebug() << "EVENT LOG RECEIVED IN CHATROOM" << mChatModel->getTitle();
|
||||||
auto event = EventLogCore::create(eventLog);
|
auto event = EventLogCore::create(eventLog);
|
||||||
mChatModelConnection->invokeToCore([this, event]() {
|
if (event->isHandled()) {
|
||||||
appendEventLogToEventLogList(event);
|
mChatModelConnection->invokeToCore([this, event]() {
|
||||||
emit lUpdateUnreadCount();
|
appendEventLogToEventLogList(event);
|
||||||
emit lUpdateLastUpdatedTime();
|
emit lUpdateUnreadCount();
|
||||||
});
|
emit lUpdateLastUpdatedTime();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Chat messages
|
||||||
mChatModelConnection->makeConnectToModel(
|
mChatModelConnection->makeConnectToModel(
|
||||||
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
&ChatModel::chatMessagesReceived, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
const std::list<std::shared_ptr<linphone::EventLog>> &eventsLog) {
|
const std::list<std::shared_ptr<linphone::EventLog>> &eventsLog) {
|
||||||
|
|
@ -196,7 +203,7 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
|
||||||
auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr;
|
auto lastMessageModel = mLastMessage ? mLastMessage->getModel() : nullptr;
|
||||||
mChatModelConnection->invokeToModel([this, lastMessageModel]() {
|
mChatModelConnection->invokeToModel([this, lastMessageModel]() {
|
||||||
auto linphoneMessage = mChatModel->getLastChatMessage();
|
auto linphoneMessage = mChatModel->getLastChatMessage();
|
||||||
if (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage) {
|
if (linphoneMessage && (!lastMessageModel || lastMessageModel->getMonitor() != linphoneMessage)) {
|
||||||
auto chatMessageCore = ChatMessageCore::create(linphoneMessage);
|
auto chatMessageCore = ChatMessageCore::create(linphoneMessage);
|
||||||
mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); });
|
mChatModelConnection->invokeToCore([this, chatMessageCore]() { setLastMessage(chatMessageCore); });
|
||||||
}
|
}
|
||||||
|
|
@ -269,6 +276,47 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mChatModelConnection->makeConnectToCore(&ChatCore::lSetSubject, [this](QString subject) {
|
||||||
|
mChatModelConnection->invokeToModel([this, subject]() { mChatModel->setSubject(subject); });
|
||||||
|
});
|
||||||
|
mChatModelConnection->makeConnectToModel(
|
||||||
|
&ChatModel::subjectChanged, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
|
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
||||||
|
QString subject = Utils::coreStringToAppString(chatRoom->getSubject());
|
||||||
|
mChatModelConnection->invokeToCore([this, subject]() { setTitle(subject); });
|
||||||
|
});
|
||||||
|
|
||||||
|
mChatModelConnection->makeConnectToModel(&ChatModel::participantAdded,
|
||||||
|
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
|
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
||||||
|
auto participants = buildParticipants(chatRoom);
|
||||||
|
mChatModelConnection->invokeToCore([this, participants]() {
|
||||||
|
mParticipants = participants;
|
||||||
|
emit participantsChanged();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mChatModelConnection->makeConnectToModel(&ChatModel::participantRemoved,
|
||||||
|
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
|
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
||||||
|
auto participants = buildParticipants(chatRoom);
|
||||||
|
mChatModelConnection->invokeToCore([this, participants]() {
|
||||||
|
mParticipants = participants;
|
||||||
|
emit participantsChanged();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mChatModelConnection->makeConnectToModel(&ChatModel::participantAdminStatusChanged,
|
||||||
|
[this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
|
const std::shared_ptr<const linphone::EventLog> &eventLog) {
|
||||||
|
auto participants = buildParticipants(chatRoom);
|
||||||
|
mChatModelConnection->invokeToCore([this, participants]() {
|
||||||
|
mParticipants = participants;
|
||||||
|
emit participantsChanged();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
mChatModelConnection->makeConnectToCore(&ChatCore::lRemoveParticipantAtIndex, [this](int index) {
|
||||||
|
mChatModelConnection->invokeToModel([this, index]() { mChatModel->removeParticipantAtIndex(index); });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime ChatCore::getLastUpdatedTime() const {
|
QDateTime ChatCore::getLastUpdatedTime() const {
|
||||||
|
|
@ -463,3 +511,34 @@ bool ChatCore::isMuted() const {
|
||||||
bool ChatCore::isEphemeralEnabled() const {
|
bool ChatCore::isEphemeralEnabled() const {
|
||||||
return mEphemeralEnabled;
|
return mEphemeralEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatCore::setMeAdmin(bool admin) {
|
||||||
|
if (mMeAdmin != admin) {
|
||||||
|
mMeAdmin = admin;
|
||||||
|
emit meAdminChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatCore::getMeAdmin() const {
|
||||||
|
return mMeAdmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList ChatCore::getParticipantsGui() const {
|
||||||
|
QVariantList result;
|
||||||
|
for (auto participantCore : mParticipants) {
|
||||||
|
auto participantGui = new ParticipantGui(participantCore);
|
||||||
|
result.append(QVariant::fromValue(participantGui));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QSharedPointer<ParticipantCore>>
|
||||||
|
ChatCore::buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom) const {
|
||||||
|
mustBeInLinphoneThread(log().arg(Q_FUNC_INFO));
|
||||||
|
QList<QSharedPointer<ParticipantCore>> result;
|
||||||
|
for (auto participant : chatRoom->getParticipants()) {
|
||||||
|
auto participantCore = ParticipantCore::create(participant);
|
||||||
|
result.append(participantCore);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
#define CHAT_CORE_H_
|
#define CHAT_CORE_H_
|
||||||
|
|
||||||
#include "core/chat/message/EventLogGui.hpp"
|
#include "core/chat/message/EventLogGui.hpp"
|
||||||
|
#include "core/participant/ParticipantCore.hpp"
|
||||||
#include "message/ChatMessageGui.hpp"
|
#include "message/ChatMessageGui.hpp"
|
||||||
#include "model/chat/ChatModel.hpp"
|
#include "model/chat/ChatModel.hpp"
|
||||||
#include "model/search/MagicSearchModel.hpp"
|
#include "model/search/MagicSearchModel.hpp"
|
||||||
|
|
@ -57,6 +58,8 @@ public:
|
||||||
Q_PROPERTY(QString sendingText READ getSendingText WRITE setSendingText NOTIFY sendingTextChanged)
|
Q_PROPERTY(QString sendingText READ getSendingText WRITE setSendingText NOTIFY sendingTextChanged)
|
||||||
Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE lEnableEphemeral NOTIFY ephemeralEnabledChanged)
|
Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE lEnableEphemeral NOTIFY ephemeralEnabledChanged)
|
||||||
Q_PROPERTY(bool muted READ isMuted WRITE lSetMuted NOTIFY mutedChanged)
|
Q_PROPERTY(bool muted READ isMuted WRITE lSetMuted NOTIFY mutedChanged)
|
||||||
|
Q_PROPERTY(bool meAdmin READ getMeAdmin WRITE setMeAdmin NOTIFY meAdminChanged)
|
||||||
|
Q_PROPERTY(QVariantList participants READ getParticipantsGui NOTIFY participantsChanged)
|
||||||
|
|
||||||
// Should be call from model Thread. Will be automatically in App thread after initialization
|
// Should be call from model Thread. Will be automatically in App thread after initialization
|
||||||
static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
|
static QSharedPointer<ChatCore> create(const std::shared_ptr<linphone::ChatRoom> &chatRoom);
|
||||||
|
|
@ -103,6 +106,9 @@ public:
|
||||||
QString getChatRoomAddress() const;
|
QString getChatRoomAddress() const;
|
||||||
QString getPeerAddress() const;
|
QString getPeerAddress() const;
|
||||||
|
|
||||||
|
bool getMeAdmin() const;
|
||||||
|
void setMeAdmin(bool admin);
|
||||||
|
|
||||||
QList<QSharedPointer<EventLogCore>> getEventLogList() const;
|
QList<QSharedPointer<EventLogCore>> getEventLogList() const;
|
||||||
void resetEventLogList(QList<QSharedPointer<EventLogCore>> list);
|
void resetEventLogList(QList<QSharedPointer<EventLogCore>> list);
|
||||||
void appendEventLogToEventLogList(QSharedPointer<EventLogCore> event);
|
void appendEventLogToEventLogList(QSharedPointer<EventLogCore> event);
|
||||||
|
|
@ -120,6 +126,9 @@ public:
|
||||||
|
|
||||||
std::shared_ptr<ChatModel> getModel() const;
|
std::shared_ptr<ChatModel> getModel() const;
|
||||||
|
|
||||||
|
QList<QSharedPointer<ParticipantCore>> buildParticipants(const std::shared_ptr<linphone::ChatRoom> &chatRoom) const;
|
||||||
|
QVariantList getParticipantsGui() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
// used to close all the notifications when one is clicked
|
// used to close all the notifications when one is clicked
|
||||||
void messageOpen();
|
void messageOpen();
|
||||||
|
|
@ -138,6 +147,8 @@ signals:
|
||||||
void sendingTextChanged(QString text);
|
void sendingTextChanged(QString text);
|
||||||
void mutedChanged();
|
void mutedChanged();
|
||||||
void ephemeralEnabledChanged();
|
void ephemeralEnabledChanged();
|
||||||
|
void meAdminChanged();
|
||||||
|
void participantsChanged();
|
||||||
|
|
||||||
void lDeleteMessage();
|
void lDeleteMessage();
|
||||||
void lDelete();
|
void lDelete();
|
||||||
|
|
@ -152,6 +163,8 @@ signals:
|
||||||
void lLeave();
|
void lLeave();
|
||||||
void lSetMuted(bool muted);
|
void lSetMuted(bool muted);
|
||||||
void lEnableEphemeral(bool enable);
|
void lEnableEphemeral(bool enable);
|
||||||
|
void lSetSubject(QString subject);
|
||||||
|
void lRemoveParticipantAtIndex(int index);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString id;
|
QString id;
|
||||||
|
|
@ -170,6 +183,8 @@ private:
|
||||||
bool mIsReadOnly = false;
|
bool mIsReadOnly = false;
|
||||||
bool mEphemeralEnabled = false;
|
bool mEphemeralEnabled = false;
|
||||||
bool mIsMuted = false;
|
bool mIsMuted = false;
|
||||||
|
bool mMeAdmin = false;
|
||||||
|
QList<QSharedPointer<ParticipantCore>> mParticipants;
|
||||||
LinphoneEnums::ChatRoomState mChatRoomState;
|
LinphoneEnums::ChatRoomState mChatRoomState;
|
||||||
std::shared_ptr<ChatModel> mChatModel;
|
std::shared_ptr<ChatModel> mChatModel;
|
||||||
QSharedPointer<ChatMessageCore> mLastMessage;
|
QSharedPointer<ChatMessageCore> mLastMessage;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,9 @@ public:
|
||||||
std::string getEventLogId();
|
std::string getEventLogId();
|
||||||
QSharedPointer<ChatMessageCore> getChatMessageCore();
|
QSharedPointer<ChatMessageCore> getChatMessageCore();
|
||||||
QSharedPointer<CallHistoryCore> getCallHistoryCore();
|
QSharedPointer<CallHistoryCore> getCallHistoryCore();
|
||||||
|
bool isHandled() const {
|
||||||
|
return mHandled;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DECLARE_ABSTRACT_OBJECT
|
DECLARE_ABSTRACT_OBJECT
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,7 @@ ParticipantCore::ParticipantCore(const std::shared_ptr<linphone::Participant> &p
|
||||||
mIsMe = ToolModel::isMe(mSipAddress);
|
mIsMe = ToolModel::isMe(mSipAddress);
|
||||||
mCreationTime = QDateTime::fromSecsSinceEpoch(participant->getCreationTime());
|
mCreationTime = QDateTime::fromSecsSinceEpoch(participant->getCreationTime());
|
||||||
mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getDisplayName());
|
mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getDisplayName());
|
||||||
if (mDisplayName.isEmpty())
|
if (mDisplayName.isEmpty()) mDisplayName = ToolModel::getDisplayName(participant->getAddress()->clone());
|
||||||
mDisplayName = Utils::coreStringToAppString(participant->getAddress()->getUsername());
|
|
||||||
for (auto &device : participant->getDevices()) {
|
for (auto &device : participant->getDevices()) {
|
||||||
auto name = Utils::coreStringToAppString(device->getName());
|
auto name = Utils::coreStringToAppString(device->getName());
|
||||||
auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly());
|
auto address = Utils::coreStringToAppString(device->getAddress()->asStringUriOnly());
|
||||||
|
|
|
||||||
|
|
@ -6245,6 +6245,11 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>Meeting</extracomment>
|
<extracomment>Meeting</extracomment>
|
||||||
<translation>Meeting</translation>
|
<translation>Meeting</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participants</source>
|
||||||
|
<extracomment>Participants</extracomment>
|
||||||
|
<translation>Participants (%1)</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>group_infos_media_docs</source>
|
<source>group_infos_media_docs</source>
|
||||||
<extracomment>Medias & documents</extracomment>
|
<extracomment>Medias & documents</extracomment>
|
||||||
|
|
@ -6300,6 +6305,64 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
||||||
<translation>Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren?</translation>
|
<translation>Alle Nachrichten werden aus dem Chat entfernt. Möchten Sie fortfahren?</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_group_call_toast_message</source>
|
||||||
|
<extracomment>"Start a group call ?"</extracomment>
|
||||||
|
<translation>Start a group call ?</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>GroupChatInfoParticipants</name>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participant_is_admin</source>
|
||||||
|
<extracomment>Admin</extracomment>
|
||||||
|
<translation>Admin</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_add_participants_title</source>
|
||||||
|
<extracomment>Add Participants</extracomment>
|
||||||
|
<translation>Add Participants</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_see_existing_contact</source>
|
||||||
|
<extracomment>"Show contact"</extracomment>
|
||||||
|
<translation>Kontakt anzeigen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_add_address_to_contacts</source>
|
||||||
|
<extracomment>"Add to contacts"</extracomment>
|
||||||
|
<translation>Zu Kontakten hinzufügen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_give_admin_rights</source>
|
||||||
|
<extracomment>"Give admin rights"</extracomment>
|
||||||
|
<translation>Give admin rights</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_admin_rights</source>
|
||||||
|
<extracomment>"Remove admin rights"</extracomment>
|
||||||
|
<translation>Remove admin rights</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_copy_sip_address</source>
|
||||||
|
<extracomment>"Copy SIP Address"</extracomment>
|
||||||
|
<translation>Copy SIP Address</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participant</source>
|
||||||
|
<extracomment>"Remove participant"</extracomment>
|
||||||
|
<translation>Remove participant</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_title</source>
|
||||||
|
<extracomment>"Remove participant ?"</extracomment>
|
||||||
|
<translation>Remove participant ?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_message</source>
|
||||||
|
<extracomment>"Participant will be removed from chat room."</extracomment>
|
||||||
|
<translation>Participant will be removed from chat room.</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
||||||
|
|
@ -6224,7 +6224,7 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<message>
|
<message>
|
||||||
<source>one_one_infos_open_contact</source>
|
<source>one_one_infos_open_contact</source>
|
||||||
<extracomment>Open contact</extracomment>
|
<extracomment>Open contact</extracomment>
|
||||||
<translation>Open contact</translation>
|
<translation>Show contact</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>one_one_infos_create_contact</source>
|
<source>one_one_infos_create_contact</source>
|
||||||
|
|
@ -6249,6 +6249,11 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>Meeting</extracomment>
|
<extracomment>Meeting</extracomment>
|
||||||
<translation>Meeting</translation>
|
<translation>Meeting</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participants</source>
|
||||||
|
<extracomment>Participants</extracomment>
|
||||||
|
<translation>Participants (%1)</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>group_infos_media_docs</source>
|
<source>group_infos_media_docs</source>
|
||||||
<extracomment>Medias & documents</extracomment>
|
<extracomment>Medias & documents</extracomment>
|
||||||
|
|
@ -6309,5 +6314,64 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
||||||
<translation>All the messages will be removed from the chat room. Do you want to continue ?</translation>
|
<translation>All the messages will be removed from the chat room. Do you want to continue ?</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_group_call_toast_message</source>
|
||||||
|
<extracomment>"Start a group call ?"</extracomment>
|
||||||
|
<translation>Start a group call ?</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>GroupChatInfoParticipants</name>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_add_participants_title</source>
|
||||||
|
<extracomment>Add Participants</extracomment>
|
||||||
|
<translation>Add Participants</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participant_is_admin</source>
|
||||||
|
<extracomment>Admin</extracomment>
|
||||||
|
<translation>Admin</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_see_existing_contact</source>
|
||||||
|
<extracomment>"Show contact"</extracomment>
|
||||||
|
<translation>Show contact</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_add_address_to_contacts</source>
|
||||||
|
<extracomment>"Add to contacts"</extracomment>
|
||||||
|
<translation>Add to contacts</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_give_admin_rights</source>
|
||||||
|
<extracomment>"Give admin rights"</extracomment>
|
||||||
|
<translation>Give admin rights</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_admin_rights</source>
|
||||||
|
<extracomment>"Remove admin rights"</extracomment>
|
||||||
|
<translation>Remove admin rights</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_copy_sip_address</source>
|
||||||
|
<extracomment>"Copy SIP Address"</extracomment>
|
||||||
|
<translation>Copy SIP Address</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participant</source>
|
||||||
|
<extracomment>"Remove participant"</extracomment>
|
||||||
|
<translation>Remove participant</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_title</source>
|
||||||
|
<extracomment>"Remove participant ?"</extracomment>
|
||||||
|
<translation>Remove participant ?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_message</source>
|
||||||
|
<extracomment>"Participant will be removed from chat room."</extracomment>
|
||||||
|
<translation>Participant will be removed from chat room.</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
||||||
|
|
@ -6146,6 +6146,11 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>Meeting</extracomment>
|
<extracomment>Meeting</extracomment>
|
||||||
<translation>Réunion</translation>
|
<translation>Réunion</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participants</source>
|
||||||
|
<extracomment>Participants</extracomment>
|
||||||
|
<translation>Participants (%1)</translation>
|
||||||
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>group_infos_media_docs</source>
|
<source>group_infos_media_docs</source>
|
||||||
<extracomment>Medias & documents</extracomment>
|
<extracomment>Medias & documents</extracomment>
|
||||||
|
|
@ -6206,5 +6211,65 @@ Failed to create 1-1 conversation with %1 !</extracomment>
|
||||||
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
<extracomment>All the messages will be removed from the chat room. Do you want to continue ?</extracomment>
|
||||||
<translation>Vous ne recevrez ni pourrez envoyer des messages dans cette conversation, quitter ?</translation>
|
<translation>Vous ne recevrez ni pourrez envoyer des messages dans cette conversation, quitter ?</translation>
|
||||||
</message>
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_group_call_toast_message</source>
|
||||||
|
<extracomment>"Start a group call ?"</extracomment>
|
||||||
|
<translation>Démarrer un appel de groupe ?</translation>
|
||||||
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>GroupChatInfoParticipants</name>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_add_participants_title</source>
|
||||||
|
<extracomment>Add Participants</extracomment>
|
||||||
|
<translation>Ajouter des Participants</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_participant_is_admin</source>
|
||||||
|
<extracomment>Admin</extracomment>
|
||||||
|
<translation>Admin</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_see_existing_contact</source>
|
||||||
|
<extracomment>"Show contact"</extracomment>
|
||||||
|
<translation>Voir le contact</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>menu_add_address_to_contacts</source>
|
||||||
|
<extracomment>"Add to contacts"</extracomment>
|
||||||
|
<translation>Ajouter aux contacts</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_give_admin_rights</source>
|
||||||
|
<extracomment>"Give admin rights"</extracomment>
|
||||||
|
<translation>Donner les droits admins</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_admin_rights</source>
|
||||||
|
<extracomment>"Remove admin rights"</extracomment>
|
||||||
|
<translation>Retirer les droits admins</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_copy_sip_address</source>
|
||||||
|
<extracomment>"Copy SIP Address"</extracomment>
|
||||||
|
<translation>Copier l’adresse SIP</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participant</source>
|
||||||
|
<extracomment>"Remove participant"</extracomment>
|
||||||
|
<translation>Retirer le participant</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_title</source>
|
||||||
|
<extracomment>"Remove participant ?"</extracomment>
|
||||||
|
<translation>Retirer le participant ?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>group_infos_remove_participants_toast_message</source>
|
||||||
|
<extracomment>"Participant will be removed from chat room."</extracomment>
|
||||||
|
<translation>La participant sere retiré de la conversation</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
|
||||||
</TS>
|
</TS>
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,15 @@ linphone::ChatRoom::State ChatModel::getState() const {
|
||||||
return mMonitor->getState();
|
return mMonitor->getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatModel::setSubject(QString subject) const {
|
||||||
|
return mMonitor->setSubject(Utils::appStringToCoreString(subject));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatModel::removeParticipantAtIndex(int index) const {
|
||||||
|
auto participant = *std::next(mMonitor->getParticipants().begin(), index);
|
||||||
|
mMonitor->removeParticipant(participant);
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------------//
|
//---------------------------------------------------------------//
|
||||||
|
|
||||||
void ChatModel::onIsComposingReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
void ChatModel::onIsComposingReceived(const std::shared_ptr<linphone::ChatRoom> &chatRoom,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ public:
|
||||||
linphone::ChatRoom::State getState() const;
|
linphone::ChatRoom::State getState() const;
|
||||||
void setMuted(bool muted);
|
void setMuted(bool muted);
|
||||||
void enableEphemeral(bool enable);
|
void enableEphemeral(bool enable);
|
||||||
|
void setSubject(QString subject) const;
|
||||||
|
void removeParticipantAtIndex(int index) const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void historyDeleted();
|
void historyDeleted();
|
||||||
|
|
|
||||||
|
|
@ -2004,4 +2004,4 @@ void Utils::setGlobalCursor(Qt::CursorShape cursor) {
|
||||||
|
|
||||||
void Utils::restoreGlobalCursor() {
|
void Utils::restoreGlobalCursor() {
|
||||||
App::getInstance()->restoreOverrideCursor();
|
App::getInstance()->restoreOverrideCursor();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ list(APPEND _LINPHONEAPP_QML_FILES
|
||||||
view/Page/Layout/Chat/GroupConversationInfos.qml
|
view/Page/Layout/Chat/GroupConversationInfos.qml
|
||||||
view/Page/Layout/Chat/OneOneConversationInfos.qml
|
view/Page/Layout/Chat/OneOneConversationInfos.qml
|
||||||
view/Page/Layout/Chat/ChatInfoActionsGroup.qml
|
view/Page/Layout/Chat/ChatInfoActionsGroup.qml
|
||||||
|
view/Page/Layout/Chat/GroupChatInfoParticipants.qml
|
||||||
|
|
||||||
view/Page/Main/AbstractMainPage.qml
|
view/Page/Main/AbstractMainPage.qml
|
||||||
view/Page/Main/Account/AccountListView.qml
|
view/Page/Main/Account/AccountListView.qml
|
||||||
|
|
|
||||||
|
|
@ -267,28 +267,21 @@ RowLayout {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: Math.round(39 * DefaultStyle.dp)
|
anchors.topMargin: Math.round(39 * DefaultStyle.dp)
|
||||||
active: true
|
sourceComponent: mainItem.chat.core.isGroupChat ? groupInfoComponent : oneToOneInfoComponent
|
||||||
property var chat: mainItem.chat
|
active: detailsPanel.visible
|
||||||
sourceComponent: chat && chat.core.isGroupChat ? groupInfoComponent : oneToOneInfoComponent
|
|
||||||
|
|
||||||
onLoaded: {
|
|
||||||
if (item && item.hasOwnProperty("chat")) {
|
|
||||||
item.chat = chat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: oneToOneInfoComponent
|
id: oneToOneInfoComponent
|
||||||
OneOneConversationInfos {
|
OneOneConversationInfos {
|
||||||
chat: contentLoader.chat
|
chatGui: mainItem.chat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: groupInfoComponent
|
id: groupInfoComponent
|
||||||
GroupConversationInfos {
|
GroupConversationInfos {
|
||||||
chat: contentLoader.chat
|
chatGui: mainItem.chat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
206
Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml
Normal file
206
Linphone/view/Page/Layout/Chat/GroupChatInfoParticipants.qml
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls.Basic as Control
|
||||||
|
import QtQuick.Dialogs
|
||||||
|
import QtQuick.Effects
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Linphone
|
||||||
|
import UtilsCpp
|
||||||
|
import SettingsCpp
|
||||||
|
import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
|
||||||
|
id: mainItem
|
||||||
|
property var title: String
|
||||||
|
property var participants
|
||||||
|
property var chatCore
|
||||||
|
signal addParticipantRequested()
|
||||||
|
|
||||||
|
function isGroupEditable() {
|
||||||
|
return chatCore && chatCore.meAdmin && !chatCore.isReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
font: Typography.h4
|
||||||
|
color: DefaultStyle.main2_600
|
||||||
|
text: title
|
||||||
|
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Math.round(9 * DefaultStyle.dp)
|
||||||
|
color: DefaultStyle.grey_100
|
||||||
|
radius: Math.round(15 * DefaultStyle.dp)
|
||||||
|
height: contentColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: contentColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Math.round(16 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: participants.length > 0
|
||||||
|
Layout.preferredHeight: Math.round(1 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: participants
|
||||||
|
delegate: RowLayout {
|
||||||
|
width: parent.width
|
||||||
|
Layout.leftMargin: Math.round(17 * DefaultStyle.dp)
|
||||||
|
Layout.rightMargin: Math.round(10 * DefaultStyle.dp)
|
||||||
|
spacing: Math.round(10 * DefaultStyle.dp)
|
||||||
|
property var participantGui: modelData
|
||||||
|
property var participantCore: participantGui.core
|
||||||
|
property var contactObj: UtilsCpp.findFriendByAddress(participantCore.sipAddress)
|
||||||
|
property var contact: contactObj?.value || null
|
||||||
|
Avatar {
|
||||||
|
contact: contactObj?.value || null
|
||||||
|
displayNameVal: contact ? "" : participantCore.displayName
|
||||||
|
Layout.preferredWidth: Math.round(45 * DefaultStyle.dp)
|
||||||
|
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Math.round(56 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Math.round(2 * DefaultStyle.dp)
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: participantCore.displayName
|
||||||
|
font: Typography.p1
|
||||||
|
color: DefaultStyle.main2_700
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
visible: participantCore.isAdmin
|
||||||
|
text: qsTr("group_infos_participant_is_admin")
|
||||||
|
font: Typography.p3
|
||||||
|
color: DefaultStyle.main2_500main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
PopupButton {
|
||||||
|
id: detailOptions
|
||||||
|
popup.x: width
|
||||||
|
popup.contentItem: FocusScope {
|
||||||
|
implicitHeight: detailsButtons.implicitHeight
|
||||||
|
implicitWidth: detailsButtons.implicitWidth
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (event.key == Qt.Key_Left
|
||||||
|
|| event.key == Qt.Key_Escape) {
|
||||||
|
detailOptions.popup.close()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
id: detailsButtons
|
||||||
|
anchors.fill: parent
|
||||||
|
IconLabelButton {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
//: "Show contact"
|
||||||
|
text: contact && contact.core && contact.core.isAppFriend ? qsTr("menu_see_existing_contact") :
|
||||||
|
//: "Add to contacts"
|
||||||
|
qsTr("menu_add_address_to_contacts")
|
||||||
|
icon.source: (contact && contact.core && contact.core.isAppFriend)
|
||||||
|
? AppIcons.adressBook
|
||||||
|
: AppIcons.plusCircle
|
||||||
|
icon.width: Math.round(32 * DefaultStyle.dp)
|
||||||
|
icon.height: Math.round(32 * DefaultStyle.dp)
|
||||||
|
onClicked: {
|
||||||
|
detailOptions.close()
|
||||||
|
if (contact && contact.core.isAppFriend)
|
||||||
|
UtilsCpp.getMainWindow().displayContactPage(participantCore.sipAddress)
|
||||||
|
else
|
||||||
|
UtilsCpp.getMainWindow().displayCreateContactPage("",participantCore.sipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
visible: mainItem.isGroupEditable()
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: participantCore.isAdmin ? qsTr("group_infos_remove_admin_rights") : qsTr("group_infos_give_admin_rights")
|
||||||
|
icon.source: AppIcons.profile
|
||||||
|
icon.width: Math.round(32 * DefaultStyle.dp)
|
||||||
|
icon.height: Math.round(32 * DefaultStyle.dp)
|
||||||
|
onClicked: {
|
||||||
|
detailOptions.close()
|
||||||
|
participantCore.isAdmin = !participantCore.isAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
visible: !contact || (contact.core && !contact.core.isAppFriend)
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("group_infos_copy_sip_address")
|
||||||
|
icon.source: AppIcons.copy
|
||||||
|
icon.width: Math.round(32 * DefaultStyle.dp)
|
||||||
|
icon.height: Math.round(32 * DefaultStyle.dp)
|
||||||
|
onClicked: {
|
||||||
|
detailOptions.close()
|
||||||
|
UtilsCpp.copyToClipboard(participantCore.sipAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Rectangle {
|
||||||
|
visible: mainItem.isGroupEditable()
|
||||||
|
color: DefaultStyle.main2_200
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: Math.round(1 * DefaultStyle.dp)
|
||||||
|
width: parent.width - Math.round(30 * DefaultStyle.dp)
|
||||||
|
Layout.leftMargin: Math.round(17 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
IconLabelButton {
|
||||||
|
visible: mainItem.isGroupEditable()
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: qsTr("group_infos_remove_participant")
|
||||||
|
icon.source: AppIcons.trashCan
|
||||||
|
icon.width: Math.round(32 * DefaultStyle.dp)
|
||||||
|
icon.height: Math.round(32 * DefaultStyle.dp)
|
||||||
|
style: ButtonStyle.hoveredBackgroundRed
|
||||||
|
onClicked: {
|
||||||
|
detailOptions.close()
|
||||||
|
UtilsCpp.getMainWindow().showConfirmationLambdaPopup(qsTr("group_infos_remove_participants_toast_title"),
|
||||||
|
qsTr("group_infos_remove_participants_toast_message"),
|
||||||
|
"",
|
||||||
|
function(confirmed) {
|
||||||
|
if (confirmed) {
|
||||||
|
mainItem.chatCore.lRemoveParticipantAtIndex(index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediumButton {
|
||||||
|
id: addParticipant
|
||||||
|
visible: mainItem.isGroupEditable()
|
||||||
|
height: Math.round(40 * DefaultStyle.dp)
|
||||||
|
icon.source: AppIcons.plusCircle
|
||||||
|
icon.width: Math.round(16 * DefaultStyle.dp)
|
||||||
|
icon.height: Math.round(16 * DefaultStyle.dp)
|
||||||
|
//: "Ajouter des participants"
|
||||||
|
text: qsTr("group_infos_add_participants_title")
|
||||||
|
style: ButtonStyle.secondary
|
||||||
|
onClicked: mainItem.addParticipantRequested()
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.bottomMargin: Math.round(17 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
visible: !addParticipant.visible
|
||||||
|
Layout.bottomMargin: Math.round(7 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import QtCore
|
import QtCore
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls.Basic as Control
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Dialogs
|
import QtQuick.Dialogs
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
@ -11,28 +11,93 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: mainItem
|
id: mainItem
|
||||||
property var chat
|
property ChatGui chatGui
|
||||||
|
property var chatCore: chatGui.core
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
displayNameVal: mainItem.chat.core.avatarUri
|
displayNameVal: mainItem.chatCore.avatarUri
|
||||||
Layout.preferredWidth: Math.round(100 * DefaultStyle.dp)
|
Layout.preferredWidth: Math.round(100 * DefaultStyle.dp)
|
||||||
Layout.preferredHeight: Math.round(100 * DefaultStyle.dp)
|
Layout.preferredHeight: Math.round(100 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
RowLayout {
|
||||||
font: Typography.p1
|
id: titleMainItem
|
||||||
color: DefaultStyle.main2_700
|
property bool isEditingSubject: false
|
||||||
text: mainItem.chat?.core.title || ""
|
property bool canEditSubject: mainItem.chatCore.meAdmin && mainItem.chatCore.isGroupChat
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Math.round(11 * DefaultStyle.dp)
|
Layout.topMargin: Math.round(11 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
function saveSubject() {
|
||||||
|
mainItem.chatCore.lSetSubject(title.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: titleMainItem.canEditSubject
|
||||||
|
Layout.preferredWidth: Math.round(18 * DefaultStyle.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: "transparent"
|
||||||
|
border.color: titleMainItem.isEditingSubject ? DefaultStyle.main1_500_main : "transparent"
|
||||||
|
border.width: 1
|
||||||
|
radius: Math.round(4 * DefaultStyle.dp)
|
||||||
|
Layout.preferredWidth: Math.round(150 * DefaultStyle.dp)
|
||||||
|
Layout.preferredHeight: title.contentHeight
|
||||||
|
anchors.margins: Math.round(2 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
TextEdit {
|
||||||
|
id: title
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
font: Typography.p1
|
||||||
|
color: DefaultStyle.main2_700
|
||||||
|
text: mainItem.chatCore.title || ""
|
||||||
|
enabled: titleMainItem.isEditingSubject
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
if (titleMainItem.isEditingSubject)
|
||||||
|
titleMainItem.saveSubject()
|
||||||
|
titleMainItem.isEditingSubject = !titleMainItem.isEditingSubject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: titleMainItem.canEditSubject
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: Math.round(18 * DefaultStyle.dp)
|
||||||
|
Layout.preferredHeight: Math.round(18 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
EffectImage {
|
||||||
|
anchors.fill: parent
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
imageSource: titleMainItem.isEditingSubject ? AppIcons.check : AppIcons.pencil
|
||||||
|
colorizationColor: titleMainItem.isEditingSubject ? DefaultStyle.main1_500_main : DefaultStyle.main2_500main
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: editMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
onClicked: {
|
||||||
|
if (titleMainItem.isEditingSubject)
|
||||||
|
titleMainItem.saveSubject()
|
||||||
|
titleMainItem.isEditingSubject = !titleMainItem.isEditingSubject
|
||||||
|
}
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
font: Typography.p3
|
font: Typography.p3
|
||||||
color: DefaultStyle.main2_700
|
color: DefaultStyle.main2_700
|
||||||
text: mainItem.chat?.core.peerAddress
|
text: mainItem.chatCore.peerAddress
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
@ -46,11 +111,11 @@ ColumnLayout {
|
||||||
height: Math.round(56 * DefaultStyle.dp)
|
height: Math.round(56 * DefaultStyle.dp)
|
||||||
button.icon.width: Math.round(24 * DefaultStyle.dp)
|
button.icon.width: Math.round(24 * DefaultStyle.dp)
|
||||||
button.icon.height: Math.round(24 * DefaultStyle.dp)
|
button.icon.height: Math.round(24 * DefaultStyle.dp)
|
||||||
button.icon.source: chat.core.muted ? AppIcons.bell : AppIcons.bellSlash
|
button.icon.source: chatCore.muted ? AppIcons.bell : AppIcons.bellSlash
|
||||||
//: "Sourdine"
|
//: "Sourdine"
|
||||||
label: qsTr("group_infos_mute")
|
label: qsTr("group_infos_mute")
|
||||||
button.onClicked: {
|
button.onClicked: {
|
||||||
chat.core.muted = !chat.core.muted
|
chatCore.muted = !chatCore.muted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LabelButton {
|
LabelButton {
|
||||||
|
|
@ -62,7 +127,21 @@ ColumnLayout {
|
||||||
//: "Appel"
|
//: "Appel"
|
||||||
label: qsTr("group_infos_call")
|
label: qsTr("group_infos_call")
|
||||||
button.onClicked: {
|
button.onClicked: {
|
||||||
//TODO
|
mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_call"),
|
||||||
|
qsTr("group_infos_group_call_toast_message"),
|
||||||
|
"",
|
||||||
|
function(confirmed) {
|
||||||
|
if (confirmed) {
|
||||||
|
const sourceList = mainItem.chatCore.participants
|
||||||
|
let addresses = [];
|
||||||
|
for (let i = 0; i < sourceList.length; ++i) {
|
||||||
|
const participantGui = sourceList[i]
|
||||||
|
const participantCore = participantGui.core
|
||||||
|
addresses.push(participantCore.sipAddress)
|
||||||
|
}
|
||||||
|
UtilsCpp.createGroupCall(mainItem.chatCore.title, addresses)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LabelButton {
|
LabelButton {
|
||||||
|
|
@ -78,94 +157,112 @@ ColumnLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatInfoActionsGroup {
|
|
||||||
Layout.leftMargin: Math.round(15 * DefaultStyle.dp)
|
|
||||||
Layout.rightMargin: Math.round(12 * DefaultStyle.dp)
|
|
||||||
Layout.topMargin: Math.round(30 * DefaultStyle.dp)
|
|
||||||
title: qsTr("group_infos_media_docs")
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
icon: AppIcons.photo,
|
|
||||||
visible: true,
|
|
||||||
text: qsTr("group_infos_media_docs"),
|
|
||||||
color: DefaultStyle.main2_600,
|
|
||||||
showRightArrow: true,
|
|
||||||
action: function() {
|
|
||||||
console.log("group_infos_shared_media")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: AppIcons.pdf,
|
|
||||||
visible: true,
|
|
||||||
text: qsTr("group_infos_shared_docs"),
|
|
||||||
color: DefaultStyle.main2_600,
|
|
||||||
showRightArrow: true,
|
|
||||||
action: function() {
|
|
||||||
console.log("Opening shared documents")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatInfoActionsGroup {
|
ScrollView {
|
||||||
Layout.leftMargin: Math.round(15 * DefaultStyle.dp)
|
id: scrollView
|
||||||
Layout.rightMargin: Math.round(12 * DefaultStyle.dp)
|
|
||||||
Layout.topMargin: Math.round(17 * DefaultStyle.dp)
|
|
||||||
title: qsTr("group_infos_other_actions")
|
|
||||||
entries: [
|
|
||||||
{
|
|
||||||
icon: AppIcons.clockCountDown,
|
|
||||||
visible: true,
|
|
||||||
text: mainItem.chat.core.ephemeralEnabled ? qsTr("group_infos_disable_ephemerals") : qsTr("group_infos_enable_ephemerals"),
|
|
||||||
color: DefaultStyle.main2_600,
|
|
||||||
showRightArrow: false,
|
|
||||||
action: function() {
|
|
||||||
mainItem.chat.core.ephemeralEnabled = !mainItem.chat.core.ephemeralEnabled
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: AppIcons.signOut,
|
|
||||||
visible: !mainItem.chat.core.isReadOnly,
|
|
||||||
text: qsTr("group_infos_leave_room"),
|
|
||||||
color: DefaultStyle.main2_600,
|
|
||||||
showRightArrow: false,
|
|
||||||
action: function() {
|
|
||||||
//: Leave Chat Room ?
|
|
||||||
mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_leave_room_toast_title"),
|
|
||||||
//: All the messages will be removed from the chat room. Do you want to continue ?
|
|
||||||
qsTr("group_infos_leave_room_toast_message"),
|
|
||||||
"",
|
|
||||||
function(confirmed) {
|
|
||||||
if (confirmed) {
|
|
||||||
mainItem.chat.core.lLeave()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: AppIcons.trashCan,
|
|
||||||
visible: true,
|
|
||||||
text: qsTr("group_infos_delete_history"),
|
|
||||||
color: DefaultStyle.danger_500main,
|
|
||||||
showRightArrow: false,
|
|
||||||
action: function() {
|
|
||||||
//: Delete history ?
|
|
||||||
mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_delete_history_toast_title"),
|
|
||||||
//: All the messages will be removed from the chat room. Do you want to continue ?
|
|
||||||
qsTr("group_infos_delete_history_toast_message"),
|
|
||||||
"",
|
|
||||||
function(confirmed) {
|
|
||||||
if (confirmed) {
|
|
||||||
mainItem.chat.core.lDeleteHistory()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Math.round(30 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
Layout.leftMargin: Math.round(15 * DefaultStyle.dp)
|
||||||
|
Layout.rightMargin: Math.round(15 * DefaultStyle.dp)
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
width: scrollView.width
|
||||||
|
|
||||||
|
GroupChatInfoParticipants {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
title: qsTr("group_infos_participants").arg(mainItem.chatCore.participants.length)
|
||||||
|
participants: mainItem.chatCore.participants
|
||||||
|
chatCore: mainItem.chatCore
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatInfoActionsGroup {
|
||||||
|
Layout.topMargin: Math.round(30 * DefaultStyle.dp)
|
||||||
|
title: qsTr("group_infos_media_docs")
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
icon: AppIcons.photo,
|
||||||
|
visible: true,
|
||||||
|
text: qsTr("group_infos_media_docs"),
|
||||||
|
color: DefaultStyle.main2_600,
|
||||||
|
showRightArrow: true,
|
||||||
|
action: function() {
|
||||||
|
console.log("group_infos_shared_media")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: AppIcons.pdf,
|
||||||
|
visible: true,
|
||||||
|
text: qsTr("group_infos_shared_docs"),
|
||||||
|
color: DefaultStyle.main2_600,
|
||||||
|
showRightArrow: true,
|
||||||
|
action: function() {
|
||||||
|
console.log("Opening shared documents")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatInfoActionsGroup {
|
||||||
|
Layout.topMargin: Math.round(17 * DefaultStyle.dp)
|
||||||
|
title: qsTr("group_infos_other_actions")
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
icon: AppIcons.clockCountDown,
|
||||||
|
visible: !mainItem.chatCore.isReadOnly,
|
||||||
|
text: mainItem.chatCore.ephemeralEnabled ? qsTr("group_infos_disable_ephemerals") : qsTr("group_infos_enable_ephemerals"),
|
||||||
|
color: DefaultStyle.main2_600,
|
||||||
|
showRightArrow: false,
|
||||||
|
action: function() {
|
||||||
|
mainItem.chatCore.ephemeralEnabled = !mainItem.chatCore.ephemeralEnabled
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: AppIcons.signOut,
|
||||||
|
visible: !mainItem.chatCore.isReadOnly,
|
||||||
|
text: qsTr("group_infos_leave_room"),
|
||||||
|
color: DefaultStyle.main2_600,
|
||||||
|
showRightArrow: false,
|
||||||
|
action: function() {
|
||||||
|
//: Leave Chat Room ?
|
||||||
|
mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_leave_room_toast_title"),
|
||||||
|
//: All the messages will be removed from the chat room. Do you want to continue ?
|
||||||
|
qsTr("group_infos_leave_room_toast_message"),
|
||||||
|
"",
|
||||||
|
function(confirmed) {
|
||||||
|
if (confirmed) {
|
||||||
|
mainItem.chatCore.lLeave()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: AppIcons.trashCan,
|
||||||
|
visible: true,
|
||||||
|
text: qsTr("group_infos_delete_history"),
|
||||||
|
color: DefaultStyle.danger_500main,
|
||||||
|
showRightArrow: false,
|
||||||
|
action: function() {
|
||||||
|
//: Delete history ?
|
||||||
|
mainWindow.showConfirmationLambdaPopup(qsTr("group_infos_delete_history_toast_title"),
|
||||||
|
//: All the messages will be removed from the chat room. Do you want to continue ?
|
||||||
|
qsTr("group_infos_delete_history_toast_message"),
|
||||||
|
"",
|
||||||
|
function(confirmed) {
|
||||||
|
if (confirmed) {
|
||||||
|
mainItem.chatCore.lDeleteHistory()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.preferredHeight: Math.round(50 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,15 @@ import 'qrc:/qt/qml/Linphone/view/Style/buttonStyle.js' as ButtonStyle
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: mainItem
|
id: mainItem
|
||||||
property var chat
|
property ChatGui chatGui
|
||||||
property var contactObj: chat ? UtilsCpp.findFriendByAddress(chat?.core.peerAddress) : null
|
property var chatCore: chatGui.core
|
||||||
|
property var contactObj: chat ? UtilsCpp.findFriendByAddress(mainItem.chatCore.peerAddress) : null
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
contact: contactObj?.value || null
|
contact: contactObj?.value || null
|
||||||
displayNameVal: contact ? "" : mainItem.chat.core.avatarUri
|
displayNameVal: contact ? "" : mainItem.chatCore.avatarUri
|
||||||
Layout.preferredWidth: Math.round(100 * DefaultStyle.dp)
|
Layout.preferredWidth: Math.round(100 * DefaultStyle.dp)
|
||||||
Layout.preferredHeight: Math.round(100 * DefaultStyle.dp)
|
Layout.preferredHeight: Math.round(100 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +27,7 @@ ColumnLayout {
|
||||||
Text {
|
Text {
|
||||||
font: Typography.p1
|
font: Typography.p1
|
||||||
color: DefaultStyle.main2_700
|
color: DefaultStyle.main2_700
|
||||||
text: mainItem.chat?.core.title || ""
|
text: mainItem.chatCore.title || ""
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Math.round(11 * DefaultStyle.dp)
|
Layout.topMargin: Math.round(11 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
@ -34,20 +35,20 @@ ColumnLayout {
|
||||||
Text {
|
Text {
|
||||||
font: Typography.p3
|
font: Typography.p3
|
||||||
color: DefaultStyle.main2_700
|
color: DefaultStyle.main2_700
|
||||||
text: mainItem.chat?.core.peerAddress
|
text: mainItem.chatCore.peerAddress
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
visible: contactObj?.value
|
||||||
font: Typography.p3
|
font: Typography.p3
|
||||||
color: contactObj?.value.core.presenceColor
|
color: contactObj?.value != null ? contactObj?.value.core.presenceColor : "transparent"
|
||||||
text: contactObj?.value.core.presenceStatus
|
text: contactObj?.value != null ? contactObj?.value.core.presenceStatus : ""
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
Layout.topMargin: Math.round(5 * DefaultStyle.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Math.round(55 * DefaultStyle.dp)
|
spacing: Math.round(55 * DefaultStyle.dp)
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
@ -69,11 +70,11 @@ ColumnLayout {
|
||||||
height: Math.round(56 * DefaultStyle.dp)
|
height: Math.round(56 * DefaultStyle.dp)
|
||||||
button.icon.width: Math.round(24 * DefaultStyle.dp)
|
button.icon.width: Math.round(24 * DefaultStyle.dp)
|
||||||
button.icon.height: Math.round(24 * DefaultStyle.dp)
|
button.icon.height: Math.round(24 * DefaultStyle.dp)
|
||||||
button.icon.source: chat.core.muted ? AppIcons.bell : AppIcons.bellSlash
|
button.icon.source: mainItem.chatCore.muted ? AppIcons.bell : AppIcons.bellSlash
|
||||||
//: "Sourdine"
|
//: "Sourdine"
|
||||||
label: qsTr("one_one_infos_mute")
|
label: qsTr("one_one_infos_mute")
|
||||||
button.onClicked: {
|
button.onClicked: {
|
||||||
chat.core.muted = !chat.core.muted
|
mainItem.chatCore.muted = !mainItem.chatCore.muted
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LabelButton {
|
LabelButton {
|
||||||
|
|
@ -126,7 +127,7 @@ ColumnLayout {
|
||||||
title: qsTr("one_one_infos_other_actions")
|
title: qsTr("one_one_infos_other_actions")
|
||||||
entries: [
|
entries: [
|
||||||
{
|
{
|
||||||
icon: AppIcons.adressBook,
|
icon: contactObj.value ? AppIcons.adressBook : AppIcons.plusCircle,
|
||||||
visible: true,
|
visible: true,
|
||||||
text: contactObj.value ? qsTr("one_one_infos_open_contact") : qsTr("one_one_infos_create_contact"),
|
text: contactObj.value ? qsTr("one_one_infos_open_contact") : qsTr("one_one_infos_create_contact"),
|
||||||
color: DefaultStyle.main2_600,
|
color: DefaultStyle.main2_600,
|
||||||
|
|
@ -136,18 +137,17 @@ ColumnLayout {
|
||||||
if (contactObj.value)
|
if (contactObj.value)
|
||||||
mainWindow.displayContactPage(contactObj.value.core.defaultAddress)
|
mainWindow.displayContactPage(contactObj.value.core.defaultAddress)
|
||||||
else
|
else
|
||||||
mainWindow.displayCreateContactPage("",chat.core.peerAddress)
|
mainWindow.displayCreateContactPage("",mainItem.chatCore.peerAddress)
|
||||||
//mainItem.createContactRequested(contactDetail.contactName, chat.core.peerAddress)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: AppIcons.clockCountDown,
|
icon: AppIcons.clockCountDown,
|
||||||
visible: true,
|
visible: true,
|
||||||
text: mainItem.chat.core.ephemeralEnabled ? qsTr("one_one_infos_disable_ephemerals") : qsTr("one_one_infos_enable_ephemerals"),
|
text: mainItem.chatCore.ephemeralEnabled ? qsTr("one_one_infos_disable_ephemerals") : qsTr("one_one_infos_enable_ephemerals"),
|
||||||
color: DefaultStyle.main2_600,
|
color: DefaultStyle.main2_600,
|
||||||
showRightArrow: false,
|
showRightArrow: false,
|
||||||
action: function() {
|
action: function() {
|
||||||
mainItem.chat.core.ephemeralEnabled = !mainItem.chat.core.ephemeralEnabled
|
mainItem.chatCore.ephemeralEnabled = !mainItem.chatCore.ephemeralEnabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -164,7 +164,7 @@ ColumnLayout {
|
||||||
"",
|
"",
|
||||||
function(confirmed) {
|
function(confirmed) {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
mainItem.chat.core.lDeleteHistory()
|
mainItem.chatCore.lDeleteHistory()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue