mirror of
https://gitlab.linphone.org/BC/public/linphone-desktop.git
synced 2026-01-17 03:18:07 +00:00
message reply
This commit is contained in:
parent
f82a4db826
commit
4a1f1a895b
16 changed files with 1691 additions and 1389 deletions
|
|
@ -76,9 +76,7 @@ void AccountDeviceList::setAccount(const QSharedPointer<AccountCore> &accountCor
|
|||
|
||||
void AccountDeviceList::refreshDevices() {
|
||||
mustBeInMainThread(log().arg(Q_FUNC_INFO));
|
||||
beginResetModel();
|
||||
clearData();
|
||||
endResetModel();
|
||||
resetData();
|
||||
if (mAccountCore) {
|
||||
auto requestDeviceList = [this] {
|
||||
if (!mAccountManagerServicesModelConnection) return;
|
||||
|
|
@ -150,14 +148,14 @@ void AccountDeviceList::setSelf(QSharedPointer<AccountDeviceList> me) {
|
|||
&AccountManagerServicesModel::requestError,
|
||||
[this](const std::shared_ptr<const linphone::AccountManagerServicesRequest> &request, int statusCode,
|
||||
const std::string &errorMessage,
|
||||
const std::shared_ptr<const linphone::Dictionary> ¶meterErrors) {
|
||||
lDebug() << "REQUEST ERROR" << errorMessage << "/" << int(request->getType());
|
||||
QString message = QString::fromStdString(errorMessage);
|
||||
if (request->getType() == linphone::AccountManagerServicesRequest::Type::GetDevicesList) {
|
||||
//: "Erreur lors de la récupération des appareils"
|
||||
message = tr("manage_account_no_device_found_error_message");
|
||||
}
|
||||
emit requestError(message);
|
||||
const std::shared_ptr<const linphone::Dictionary> ¶meterErrors) {
|
||||
lDebug() << "REQUEST ERROR" << errorMessage << "/" << int(request->getType());
|
||||
QString message = QString::fromStdString(errorMessage);
|
||||
if (request->getType() == linphone::AccountManagerServicesRequest::Type::GetDevicesList) {
|
||||
//: "Erreur lors de la récupération des appareils"
|
||||
message = tr("manage_account_no_device_found_error_message");
|
||||
}
|
||||
emit requestError(message);
|
||||
});
|
||||
mAccountManagerServicesModelConnection->makeConnectToModel(
|
||||
&AccountManagerServicesModel::devicesListFetched,
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
fromAddress->clean();
|
||||
mFromAddress = Utils::coreStringToAppString(fromAddress->asStringUriOnly());
|
||||
mFromName = ToolModel::getDisplayName(chatmessage->getFromAddress()->clone());
|
||||
mToName = ToolModel::getDisplayName(chatmessage->getToAddress()->clone());
|
||||
|
||||
auto chatroom = chatmessage->getChatRoom();
|
||||
mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) &&
|
||||
|
|
@ -166,6 +167,13 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
|
|||
|
||||
mIsForward = chatmessage->isForward();
|
||||
mIsReply = chatmessage->isReply();
|
||||
if (mIsReply) {
|
||||
auto replymessage = chatmessage->getReplyMessage();
|
||||
if (replymessage) {
|
||||
mReplyText = ToolModel::getMessageFromContent(replymessage->getContents());
|
||||
if (mIsFromChatGroup) mRepliedToName = ToolModel::getDisplayName(replymessage->getToAddress()->clone());
|
||||
}
|
||||
}
|
||||
mImdnStatusList = computeDeliveryStatus(chatmessage);
|
||||
}
|
||||
|
||||
|
|
@ -380,6 +388,10 @@ QString ChatMessageCore::getToAddress() const {
|
|||
return mToAddress;
|
||||
}
|
||||
|
||||
QString ChatMessageCore::getToName() const {
|
||||
return mToName;
|
||||
}
|
||||
|
||||
QString ChatMessageCore::getMessageId() const {
|
||||
return mMessageId;
|
||||
}
|
||||
|
|
@ -575,4 +587,4 @@ std::shared_ptr<ChatMessageModel> ChatMessageCore::getModel() const {
|
|||
|
||||
ChatMessageContentGui *ChatMessageCore::getVoiceRecordingContent() const {
|
||||
return new ChatMessageContentGui(mVoiceRecordingContent);
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +100,8 @@ class ChatMessageCore : public QObject, public AbstractObject {
|
|||
QStringList reactionsSingletonAsStrings READ getReactionsSingletonAsStrings NOTIFY singletonReactionMapChanged)
|
||||
Q_PROPERTY(bool isForward MEMBER mIsForward CONSTANT)
|
||||
Q_PROPERTY(bool isReply MEMBER mIsReply CONSTANT)
|
||||
Q_PROPERTY(QString replyText MEMBER mReplyText CONSTANT)
|
||||
Q_PROPERTY(QString repliedToName MEMBER mRepliedToName CONSTANT)
|
||||
Q_PROPERTY(bool hasFileContent MEMBER mHasFileContent CONSTANT)
|
||||
Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT)
|
||||
Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT)
|
||||
|
|
@ -123,6 +125,7 @@ public:
|
|||
QString getFromAddress() const;
|
||||
QString getFromName() const;
|
||||
QString getToAddress() const;
|
||||
QString getToName() const;
|
||||
QString getMessageId() const;
|
||||
|
||||
bool isRemoteMessage() const;
|
||||
|
|
@ -182,6 +185,7 @@ private:
|
|||
QString mFromAddress;
|
||||
QString mToAddress;
|
||||
QString mFromName;
|
||||
QString mToName;
|
||||
QString mPeerName;
|
||||
QString mMessageId;
|
||||
QString mOwnReaction;
|
||||
|
|
@ -194,6 +198,8 @@ private:
|
|||
bool mIsRead = false;
|
||||
bool mIsForward = false;
|
||||
bool mIsReply = false;
|
||||
QString mReplyText;
|
||||
QString mRepliedToName;
|
||||
bool mHasFileContent = false;
|
||||
bool mIsCalendarInvite = false;
|
||||
bool mIsVoiceRecording = false;
|
||||
|
|
|
|||
|
|
@ -43,11 +43,11 @@ EventLogCore::EventLogCore(const std::shared_ptr<const linphone::EventLog> &even
|
|||
} else if (eventLog->getCallLog()) {
|
||||
mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog());
|
||||
mEventId = Utils::coreStringToAppString(eventLog->getCallLog()->getCallId());
|
||||
} else { // getNotifyId
|
||||
}
|
||||
if (mEventId.isEmpty()) { // getNotifyId
|
||||
QString type = QString::fromLatin1(
|
||||
QMetaEnum::fromType<LinphoneEnums::EventLogType>().valueToKey(static_cast<int>(mEventLogType)));
|
||||
mEventId = type + QString::number(static_cast<qint64>(eventLog->getCreationTime()));
|
||||
;
|
||||
computeEvent(eventLog);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -137,6 +137,11 @@ ChatModel::createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder>
|
|||
return mMonitor->createVoiceRecordingMessage(recorder);
|
||||
}
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage>
|
||||
ChatModel::createReplyMessage(const std::shared_ptr<linphone::ChatMessage> &message) {
|
||||
return mMonitor->createReplyMessage(message);
|
||||
}
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage> ChatModel::createTextMessageFromText(QString text) {
|
||||
return mMonitor->createMessageFromUtf8(Utils::appStringToCoreString(text));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ public:
|
|||
void leave();
|
||||
std::shared_ptr<linphone::ChatMessage>
|
||||
createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder> &recorder);
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage> createReplyMessage(const std::shared_ptr<linphone::ChatMessage> &message);
|
||||
|
||||
std::shared_ptr<linphone::ChatMessage> createTextMessageFromText(QString text);
|
||||
std::shared_ptr<linphone::ChatMessage> createMessage(QString text,
|
||||
QList<std::shared_ptr<ChatMessageContentModel>> filesContent);
|
||||
|
|
|
|||
|
|
@ -388,11 +388,13 @@ bool ToolModel::friendIsInFriendList(const std::shared_ptr<linphone::FriendList>
|
|||
|
||||
QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) {
|
||||
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
|
||||
QString res;
|
||||
for (auto &content : contents) {
|
||||
if (content->isText()) {
|
||||
return Utils::coreStringToAppString(content->getUtf8Text());
|
||||
} else if (content->isFile()) {
|
||||
return Utils::coreStringToAppString(content->getName());
|
||||
if (res.isEmpty()) res.append(Utils::coreStringToAppString(content->getName()));
|
||||
else res.append(", " + Utils::coreStringToAppString(content->getName()));
|
||||
} else if (content->isIcalendar()) {
|
||||
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
|
||||
auto conferenceInfoCore = ConferenceInfoCore::create(conferenceInfo);
|
||||
|
|
@ -406,7 +408,7 @@ QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Con
|
|||
return getMessageFromContent(content->getParts());
|
||||
}
|
||||
}
|
||||
return QString("");
|
||||
return res;
|
||||
}
|
||||
|
||||
// Load downloaded codecs like OpenH264 (needs to be after core is created and has loaded its plugins, as
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "core/call/CallGui.hpp"
|
||||
#include "core/chat/ChatCore.hpp"
|
||||
#include "core/chat/ChatGui.hpp"
|
||||
#include "core/chat/message/ChatMessageGui.hpp"
|
||||
#include "core/conference/ConferenceCore.hpp"
|
||||
#include "core/conference/ConferenceInfoCore.hpp"
|
||||
#include "core/conference/ConferenceInfoGui.hpp"
|
||||
|
|
@ -1950,6 +1951,50 @@ QString Utils::getSafeFilePath(const QString &filePath, bool *soFarSoGood) {
|
|||
return QString("");
|
||||
}
|
||||
|
||||
void Utils::sendReplyMessage(ChatMessageGui *message, ChatGui *chatGui, QString text, QVariantList files) {
|
||||
auto chatModel = chatGui && chatGui->mCore ? chatGui->mCore->getModel() : nullptr;
|
||||
auto chatMessageModel = message && message->mCore ? message->mCore->getModel() : nullptr;
|
||||
if (!chatModel || !chatMessageModel) {
|
||||
//: Cannot reply to invalid message
|
||||
QString error = !chatMessageModel ? tr("chatMessage_error")
|
||||
//: Error in the chat
|
||||
: tr("chat_error");
|
||||
//: Error
|
||||
showInformationPopup(tr("info_popup_error_title"),
|
||||
//: Could not send voice message : %1
|
||||
tr("info_popup_send_voice_message_error_message").arg(error));
|
||||
return;
|
||||
}
|
||||
QList<std::shared_ptr<ChatMessageContentModel>> filesContent;
|
||||
for (auto &file : files) {
|
||||
auto contentGui = qvariant_cast<ChatMessageContentGui *>(file);
|
||||
if (contentGui) {
|
||||
auto contentCore = contentGui->mCore;
|
||||
filesContent.append(contentCore->getContentModel());
|
||||
}
|
||||
}
|
||||
App::postModelAsync([chatModel, chatMessageModel, text, filesContent] {
|
||||
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
|
||||
auto chat = chatModel->getMonitor();
|
||||
auto messageToReplyTo = chatMessageModel->getMonitor();
|
||||
auto linMessage = chatModel->createReplyMessage(messageToReplyTo);
|
||||
if (linMessage) {
|
||||
linMessage->addUtf8TextContent(Utils::appStringToCoreString(text));
|
||||
for (auto &content : filesContent) {
|
||||
linMessage->addFileContent(content->getContent());
|
||||
}
|
||||
linMessage->send();
|
||||
} else {
|
||||
App::postCoreAsync([] {
|
||||
//: Error
|
||||
showInformationPopup(tr("info_popup_error_title"),
|
||||
//: Failed to create message from record
|
||||
tr("info_popup_send_voice_message_sending_error_message"));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VariantObject *Utils::createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui) {
|
||||
VariantObject *data = new VariantObject("createVoiceRecordingMessage");
|
||||
if (!data) return nullptr;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class ConferenceCore;
|
|||
class ParticipantDeviceCore;
|
||||
class DownloadablePayloadTypeCore;
|
||||
class ChatGui;
|
||||
class ChatMessageGui;
|
||||
class RecorderGui;
|
||||
|
||||
class Utils : public QObject, public AbstractObject {
|
||||
|
|
@ -176,6 +177,8 @@ public:
|
|||
Q_INVOKABLE static QString toTimeString(QDateTime date, const QString &format = "hh:mm:ss");
|
||||
|
||||
Q_INVOKABLE static VariantObject *createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui);
|
||||
Q_INVOKABLE static void
|
||||
sendReplyMessage(ChatMessageGui *message, ChatGui *chatGui, QString text, QVariantList files);
|
||||
Q_INVOKABLE static void sendVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui);
|
||||
|
||||
// QDir findDirectoryByName(QString startPath, QString name);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ Control.Control {
|
|||
property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
|
||||
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
|
||||
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : false
|
||||
property bool isReply: chatMessage? chatMessage.core.isReply : false
|
||||
property string replyText: chatMessage? chatMessage.core.replyText : false
|
||||
property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
|
||||
hoverEnabled: true
|
||||
property bool linkHovered: false
|
||||
|
|
@ -30,6 +32,7 @@ Control.Control {
|
|||
signal isFileHoveringChanged(bool isFileHovering)
|
||||
signal showReactionsForMessageRequested()
|
||||
signal showImdnStatusForMessageRequested()
|
||||
signal replyToMessageRequested()
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
|
|
@ -41,52 +44,119 @@ Control.Control {
|
|||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: mainItem.isRemoteMessage ? 26 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 26 * DefaultStyle.dp
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
Text {
|
||||
id: fromNameText
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) + avatar.width : 0
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage && mainItem.isFirstMessage && !replyLayout.visible
|
||||
maximumLineCount: 1
|
||||
width: implicitWidth
|
||||
x: mapToItem(this, chatBubble.x, chatBubble.y).x
|
||||
text: mainItem.chatMessage.core.fromName
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 0
|
||||
Text {
|
||||
id: fromNameText
|
||||
Layout.alignment: Qt.AlignTop
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage && mainItem.isFirstMessage
|
||||
// anchors.top: parent.top
|
||||
// anchors.left: parent.left
|
||||
// anchors.leftMargin: avatar.width// mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
maximumLineCount: 1
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
width: implicitWidth
|
||||
x: mapToItem(this, chatBubble.x, chatBubble.y).x
|
||||
text: mainItem.chatMessage.core.fromName
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
RowLayout {
|
||||
id: replyLayout
|
||||
visible: mainItem.isReply
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) + avatar.width : 0
|
||||
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
|
||||
ColumnLayout {
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
RowLayout {
|
||||
id: replyLabel
|
||||
spacing: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
EffectImage {
|
||||
imageSource: AppIcons.reply
|
||||
colorizationColor: DefaultStyle.main2_500main
|
||||
Layout.preferredWidth: Math.round(12 * DefaultStyle.dp)
|
||||
Layout.preferredHeight: Math.round(12 * DefaultStyle.dp)
|
||||
}
|
||||
Text {
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft: Qt.AlignRight
|
||||
text: mainItem.isRemoteMessage
|
||||
? mainItem.chatMessage.core.repliedToName !== ""
|
||||
//: %1 replied to %2
|
||||
? qsTr("chat_message_remote_replied_to").arg(mainItem.chatMessage.core.fromName).arg(mainItem.chatMessage.core.repliedToName)
|
||||
//: %1 replied
|
||||
: qsTr("chat_message_remote_replied").arg(mainItem.chatMessage.core.fromName)
|
||||
: mainItem.chatMessage.core.repliedToName !== ""
|
||||
//: You replied to %1
|
||||
? qsTr("chat_message_user_replied_to").arg(mainItem.chatMessage.core.repliedToName)
|
||||
//: You replied
|
||||
: qsTr("chat_message_user_replied")
|
||||
color: DefaultStyle.main2_600
|
||||
font {
|
||||
pixelSize: Typography.p4.pixelSize
|
||||
weight: Typography.p4.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
Control.Control {
|
||||
id: replyMessage
|
||||
visible: mainItem.replyText !== ""
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(19 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(18 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(18 * DefaultStyle.dp)
|
||||
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_200
|
||||
radius: Math.round(16 * DefaultStyle.dp)
|
||||
}
|
||||
contentItem: Text {
|
||||
Layout.fillWidth: true
|
||||
text: mainItem.replyText
|
||||
color: DefaultStyle.main2_800
|
||||
font {
|
||||
pixelSize: Typography.p1.pixelSize
|
||||
weight: Typography.p1.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item{Layout.fillWidth: true}
|
||||
}
|
||||
RowLayout {
|
||||
id: bubbleLayout
|
||||
z: replyLayout.z + 1
|
||||
spacing: 0
|
||||
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
|
||||
Layout.topMargin: replyMessage.visible ? Math.round(-20 * DefaultStyle.dp) : 0
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
visible: mainItem.isFromChatGroup && mainItem.isRemoteMessage
|
||||
Layout.preferredWidth: mainItem.isRemoteMessage ? 26 * DefaultStyle.dp : 0
|
||||
Layout.preferredHeight: 26 * DefaultStyle.dp
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
_address: chatMessage ? chatMessage.core.fromAddress : ""
|
||||
}
|
||||
Item {
|
||||
id: bubbleContainer
|
||||
// Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
|
||||
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
|
||||
Layout.preferredHeight: childrenRect.height
|
||||
Layout.preferredWidth: childrenRect.width
|
||||
|
||||
Control.Control {
|
||||
id: chatBubble
|
||||
spacing: Math.round(2 * DefaultStyle.dp)
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(6 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(12 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(12 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(18 * DefaultStyle.dp)
|
||||
rightPadding: Math.round(18 * DefaultStyle.dp)
|
||||
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth)
|
||||
|
||||
MouseArea { // Default mouse area. Each sub bubble can control the mouse and pass on to the main mouse handler. Child bubble mouse area must cover the entire bubble.
|
||||
|
|
@ -133,6 +203,7 @@ Control.Control {
|
|||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: false
|
||||
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
|
||||
Text {
|
||||
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM")
|
||||
|
|
@ -217,118 +288,130 @@ Control.Control {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: actionsLayout
|
||||
visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered || emojiButton.popup.opened
|
||||
Layout.leftMargin: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.rightMargin: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
// Layout.fillWidth: true
|
||||
spacing: Math.round(7 * DefaultStyle.dp)
|
||||
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
|
||||
PopupButton {
|
||||
id: optionsMenu
|
||||
popup.padding: 0
|
||||
popup.contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
text: chatBubbleContent.selectedText != ""
|
||||
//: "Copy selection"
|
||||
? qsTr("chat_message_copy_selection")
|
||||
//: "Copy"
|
||||
: qsTr("chat_message_copy")
|
||||
icon.source: AppIcons.copy
|
||||
// spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
var success = UtilsCpp.copyToClipboard(chatBubbleContent.selectedText != "" ? chatBubbleContent.selectedText : mainItem.chatMessage.core.text)
|
||||
//: Copied
|
||||
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
||||
//: "to clipboard"
|
||||
qsTr("chat_message_copied_to_clipboard_toast"))
|
||||
optionsMenu.close()
|
||||
}
|
||||
}
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: "See message status"
|
||||
text: qsTr("chat_message_see_status")
|
||||
icon.source: AppIcons.chatTeardropText
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
mainItem.showImdnStatusForMessageRequested()
|
||||
optionsMenu.close()
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(1, Math.round(1 * DefaultStyle.dp))
|
||||
color: DefaultStyle.main2_400
|
||||
}
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: "Delete"
|
||||
text: qsTr("chat_message_delete")
|
||||
icon.source: AppIcons.trashCan
|
||||
// spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
mainItem.messageDeletionRequested()
|
||||
optionsMenu.close()
|
||||
}
|
||||
style: ButtonStyle.hoveredBackgroundRed
|
||||
}
|
||||
}
|
||||
}
|
||||
PopupButton {
|
||||
id: emojiButton
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.smiley
|
||||
popup.contentItem: RowLayout {
|
||||
Repeater {
|
||||
model: ConstantsCpp.reactionsList
|
||||
delegate: Button {
|
||||
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData)
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_200
|
||||
radius: parent.width * 4
|
||||
visible: mainItem.ownReaction === modelData
|
||||
}
|
||||
RowLayout {
|
||||
id: actionsLayout
|
||||
visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered || emojiButton.popup.opened
|
||||
Layout.leftMargin: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.rightMargin: Math.round(8 * DefaultStyle.dp)
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
// Layout.fillWidth: true
|
||||
spacing: Math.round(7 * DefaultStyle.dp)
|
||||
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
|
||||
PopupButton {
|
||||
id: optionsMenu
|
||||
popup.padding: 0
|
||||
popup.contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: "Reception info"
|
||||
text: qsTr("chat_message_reception_info")
|
||||
icon.source: AppIcons.chatTeardropText
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
if(modelData) {
|
||||
if (mainItem.ownReaction === modelData) mainItem.chatMessage.core.lRemoveReaction()
|
||||
else mainItem.chatMessage.core.lSendReaction(modelData)
|
||||
}
|
||||
emojiButton.close()
|
||||
mainItem.showImdnStatusForMessageRequested()
|
||||
optionsMenu.close()
|
||||
}
|
||||
}
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: Reply
|
||||
text: qsTr("chat_message_reply")
|
||||
icon.source: AppIcons.reply
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
mainItem.replyToMessageRequested()
|
||||
optionsMenu.close()
|
||||
}
|
||||
}
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
text: chatBubbleContent.selectedText != ""
|
||||
//: "Copy selection"
|
||||
? qsTr("chat_message_copy_selection")
|
||||
//: "Copy"
|
||||
: qsTr("chat_message_copy")
|
||||
icon.source: AppIcons.copy
|
||||
// spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
var success = UtilsCpp.copyToClipboard(chatBubbleContent.selectedText != "" ? chatBubbleContent.selectedText : mainItem.chatMessage.core.text)
|
||||
//: Copied
|
||||
if (success) UtilsCpp.showInformationPopup(qsTr("chat_message_copied_to_clipboard_title"),
|
||||
//: "to clipboard"
|
||||
qsTr("chat_message_copied_to_clipboard_toast"))
|
||||
optionsMenu.close()
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.min(1, Math.round(1 * DefaultStyle.dp))
|
||||
color: DefaultStyle.main2_400
|
||||
}
|
||||
IconLabelButton {
|
||||
inverseLayout: true
|
||||
//: "Delete"
|
||||
text: qsTr("chat_message_delete")
|
||||
icon.source: AppIcons.trashCan
|
||||
// spacing: Math.round(10 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(45 * DefaultStyle.dp)
|
||||
onClicked: {
|
||||
mainItem.messageDeletionRequested()
|
||||
optionsMenu.close()
|
||||
}
|
||||
style: ButtonStyle.hoveredBackgroundRed
|
||||
}
|
||||
}
|
||||
PopupButton {
|
||||
id: emojiPickerButton
|
||||
icon.source: AppIcons.plusCircle
|
||||
popup.width: Math.round(393 * DefaultStyle.dp)
|
||||
popup.height: Math.round(291 * DefaultStyle.dp)
|
||||
popup.contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
onEmojiClicked: (emoji) => {
|
||||
if (mainItem.chatMessage) {
|
||||
if (mainItem.ownReaction === emoji) mainItem.chatMessage.core.lRemoveReaction()
|
||||
else mainItem.chatMessage.core.lSendReaction(emoji)
|
||||
}
|
||||
PopupButton {
|
||||
id: emojiButton
|
||||
style: ButtonStyle.noBackground
|
||||
icon.source: AppIcons.smiley
|
||||
popup.contentItem: RowLayout {
|
||||
Repeater {
|
||||
model: ConstantsCpp.reactionsList
|
||||
delegate: Button {
|
||||
text: UtilsCpp.encodeEmojiToQmlRichFormat(modelData)
|
||||
background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: DefaultStyle.grey_200
|
||||
radius: parent.width * 4
|
||||
visible: mainItem.ownReaction === modelData
|
||||
}
|
||||
onClicked: {
|
||||
if(modelData) {
|
||||
if (mainItem.ownReaction === modelData) mainItem.chatMessage.core.lRemoveReaction()
|
||||
else mainItem.chatMessage.core.lSendReaction(modelData)
|
||||
}
|
||||
emojiButton.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
PopupButton {
|
||||
id: emojiPickerButton
|
||||
icon.source: AppIcons.plusCircle
|
||||
popup.width: Math.round(393 * DefaultStyle.dp)
|
||||
popup.height: Math.round(291 * DefaultStyle.dp)
|
||||
popup.contentItem: EmojiPicker {
|
||||
id: emojiPicker
|
||||
onEmojiClicked: (emoji) => {
|
||||
if (mainItem.chatMessage) {
|
||||
if (mainItem.ownReaction === emoji) mainItem.chatMessage.core.lRemoveReaction()
|
||||
else mainItem.chatMessage.core.lSendReaction(emoji)
|
||||
}
|
||||
emojiPickerButton.close()
|
||||
emojiButton.close()
|
||||
}
|
||||
emojiPickerButton.close()
|
||||
emojiButton.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Item{Layout.fillWidth: true}
|
||||
}
|
||||
Item{Layout.fillWidth: true}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ ListView {
|
|||
property color backgroundColor
|
||||
signal showReactionsForMessageRequested(ChatMessageGui chatMessage)
|
||||
signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage)
|
||||
signal replyToMessageRequested(ChatMessageGui chatMessage)
|
||||
|
||||
Component.onCompleted: {
|
||||
var index = eventLogProxy.findFirstUnreadIndex()
|
||||
|
|
@ -137,6 +138,7 @@ ListView {
|
|||
onMessageDeletionRequested: modelData.core.lDelete()
|
||||
onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(modelData)
|
||||
onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(modelData)
|
||||
onReplyToMessageRequested: mainItem.replyToMessageRequested(modelData)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ Control.Control {
|
|||
function _emitFiles (files) {
|
||||
// Filtering files, other urls are forbidden.
|
||||
files = files.reduce(function (files, file) {
|
||||
console.log("dropping", file.toString())
|
||||
if (file.toString().startsWith("file:")) {
|
||||
files.push(Utils.getSystemPathFromUri(file))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ RowLayout {
|
|||
property var contact: contactObj?.value || null
|
||||
property CallGui call
|
||||
property alias callHeaderContent: splitPanel.headerContent
|
||||
property bool replyingToMessage: false
|
||||
spacing: 0
|
||||
|
||||
signal oneOneCall(bool video)
|
||||
|
|
@ -161,6 +162,10 @@ RowLayout {
|
|||
contentLoader.showingImdnStatus = true
|
||||
detailsPanel.visible = true
|
||||
}
|
||||
onReplyToMessageRequested: (chatMessage) => {
|
||||
mainItem.chatMessage = chatMessage
|
||||
mainItem.replyingToMessage = true
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: emojiPickerPopup
|
||||
|
|
@ -208,9 +213,9 @@ RowLayout {
|
|||
}
|
||||
Control.Control {
|
||||
id: selectedFilesArea
|
||||
visible: selectedFiles.count > 0
|
||||
visible: selectedFiles.count > 0 || mainItem.replyingToMessage
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
|
||||
Layout.preferredHeight: implicitHeight
|
||||
topPadding: Math.round(12 * DefaultStyle.dp)
|
||||
bottomPadding: Math.round(12 * DefaultStyle.dp)
|
||||
leftPadding: Math.round(19 * DefaultStyle.dp)
|
||||
|
|
@ -225,6 +230,7 @@ RowLayout {
|
|||
style: ButtonStyle.noBackground
|
||||
onClicked: {
|
||||
contents.clear()
|
||||
mainItem.replyingToMessage = false
|
||||
}
|
||||
}
|
||||
background: Item{
|
||||
|
|
@ -233,7 +239,6 @@ RowLayout {
|
|||
color: DefaultStyle.grey_0
|
||||
border.color: DefaultStyle.main2_100
|
||||
border.width: Math.round(2 * DefaultStyle.dp)
|
||||
radius: Math.round(20 * DefaultStyle.dp)
|
||||
height: parent.height / 2
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
|
|
@ -246,37 +251,73 @@ RowLayout {
|
|||
height: 2 * parent.height / 3
|
||||
}
|
||||
}
|
||||
contentItem: ListView {
|
||||
id: selectedFiles
|
||||
orientation: ListView.Horizontal
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
model: ChatMessageContentProxy {
|
||||
id: contents
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
}
|
||||
delegate: Item {
|
||||
width: Math.round(80 * DefaultStyle.dp)
|
||||
height: Math.round(80 * DefaultStyle.dp)
|
||||
FileView {
|
||||
contentGui: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: Math.round(69 * DefaultStyle.dp)
|
||||
height: Math.round(69 * DefaultStyle.dp)
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Math.round(5 * DefaultStyle.dp)
|
||||
ColumnLayout {
|
||||
id: replyLayout
|
||||
spacing: 0
|
||||
visible: mainItem.chatMessage && mainItem.replyingToMessage
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
//: Reply to %1
|
||||
text: mainItem.chatMessage ? qsTr("reply_to_label").arg(UtilsCpp.boldTextPart(mainItem.chatMessage.core.fromName, mainItem.chatMessage.core.fromName)) : ""
|
||||
color: DefaultStyle.main2_500main
|
||||
font {
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
}
|
||||
}
|
||||
RoundButton {
|
||||
icon.source: AppIcons.closeX
|
||||
icon.width: Math.round(12 * DefaultStyle.dp)
|
||||
icon.height: Math.round(12 * DefaultStyle.dp)
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
style: ButtonStyle.numericPad
|
||||
shadowEnabled: true
|
||||
padding: Math.round(3 * DefaultStyle.dp)
|
||||
onClicked: contents.removeContent(modelData)
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: mainItem.chatMessage ? mainItem.chatMessage.core.text : ""
|
||||
color: DefaultStyle.main2_400
|
||||
font {
|
||||
pixelSize: Typography.p3.pixelSize
|
||||
weight: Typography.p3.weight
|
||||
}
|
||||
}
|
||||
}
|
||||
Control.ScrollBar.horizontal: selectedFilesScrollbar
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
visible: replyLayout.visible && selectedFiles.visible
|
||||
color: DefaultStyle.main2_300
|
||||
Layout.preferredHeight: Math.max(1, Math.round(1 * DefaultStyle.dp))
|
||||
}
|
||||
ListView {
|
||||
id: selectedFiles
|
||||
orientation: ListView.Horizontal
|
||||
visible: count > 0
|
||||
spacing: Math.round(16 * DefaultStyle.dp)
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
|
||||
model: ChatMessageContentProxy {
|
||||
id: contents
|
||||
filterType: ChatMessageContentProxy.FilterContentType.File
|
||||
}
|
||||
delegate: Item {
|
||||
width: Math.round(80 * DefaultStyle.dp)
|
||||
height: Math.round(80 * DefaultStyle.dp)
|
||||
FileView {
|
||||
contentGui: modelData
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
width: Math.round(69 * DefaultStyle.dp)
|
||||
height: Math.round(69 * DefaultStyle.dp)
|
||||
}
|
||||
RoundButton {
|
||||
icon.source: AppIcons.closeX
|
||||
icon.width: Math.round(12 * DefaultStyle.dp)
|
||||
icon.height: Math.round(12 * DefaultStyle.dp)
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
style: ButtonStyle.numericPad
|
||||
shadowEnabled: true
|
||||
padding: Math.round(3 * DefaultStyle.dp)
|
||||
onClicked: contents.removeContent(modelData)
|
||||
}
|
||||
}
|
||||
Control.ScrollBar.horizontal: selectedFilesScrollbar
|
||||
}
|
||||
}
|
||||
ScrollBar {
|
||||
id: selectedFilesScrollbar
|
||||
|
|
@ -304,7 +345,11 @@ RowLayout {
|
|||
}
|
||||
onSendMessage: {
|
||||
var filesContents = contents.getAll()
|
||||
if (filesContents.length === 0)
|
||||
if (mainItem.replyingToMessage) {
|
||||
mainItem.replyingToMessage = false
|
||||
UtilsCpp.sendReplyMessage(mainItem.chatMessage, mainItem.chat, text, filesContents)
|
||||
}
|
||||
else if (filesContents.length === 0)
|
||||
mainItem.chat.core.lSendTextMessage(text)
|
||||
else mainItem.chat.core.lSendMessage(text, filesContents)
|
||||
contents.clear()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue