message reply

This commit is contained in:
Gaelle Braud 2025-06-23 14:36:54 +02:00
parent f82a4db826
commit 4a1f1a895b
16 changed files with 1691 additions and 1389 deletions

View file

@ -76,9 +76,7 @@ void AccountDeviceList::setAccount(const QSharedPointer<AccountCore> &accountCor
void AccountDeviceList::refreshDevices() { void AccountDeviceList::refreshDevices() {
mustBeInMainThread(log().arg(Q_FUNC_INFO)); mustBeInMainThread(log().arg(Q_FUNC_INFO));
beginResetModel(); resetData();
clearData();
endResetModel();
if (mAccountCore) { if (mAccountCore) {
auto requestDeviceList = [this] { auto requestDeviceList = [this] {
if (!mAccountManagerServicesModelConnection) return; if (!mAccountManagerServicesModelConnection) return;

View file

@ -114,6 +114,7 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
fromAddress->clean(); fromAddress->clean();
mFromAddress = Utils::coreStringToAppString(fromAddress->asStringUriOnly()); mFromAddress = Utils::coreStringToAppString(fromAddress->asStringUriOnly());
mFromName = ToolModel::getDisplayName(chatmessage->getFromAddress()->clone()); mFromName = ToolModel::getDisplayName(chatmessage->getFromAddress()->clone());
mToName = ToolModel::getDisplayName(chatmessage->getToAddress()->clone());
auto chatroom = chatmessage->getChatRoom(); auto chatroom = chatmessage->getChatRoom();
mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) && mIsFromChatGroup = chatroom->hasCapability((int)linphone::ChatRoom::Capabilities::Conference) &&
@ -166,6 +167,13 @@ ChatMessageCore::ChatMessageCore(const std::shared_ptr<linphone::ChatMessage> &c
mIsForward = chatmessage->isForward(); mIsForward = chatmessage->isForward();
mIsReply = chatmessage->isReply(); 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); mImdnStatusList = computeDeliveryStatus(chatmessage);
} }
@ -380,6 +388,10 @@ QString ChatMessageCore::getToAddress() const {
return mToAddress; return mToAddress;
} }
QString ChatMessageCore::getToName() const {
return mToName;
}
QString ChatMessageCore::getMessageId() const { QString ChatMessageCore::getMessageId() const {
return mMessageId; return mMessageId;
} }

View file

@ -100,6 +100,8 @@ class ChatMessageCore : public QObject, public AbstractObject {
QStringList reactionsSingletonAsStrings READ getReactionsSingletonAsStrings NOTIFY singletonReactionMapChanged) QStringList reactionsSingletonAsStrings READ getReactionsSingletonAsStrings NOTIFY singletonReactionMapChanged)
Q_PROPERTY(bool isForward MEMBER mIsForward CONSTANT) Q_PROPERTY(bool isForward MEMBER mIsForward CONSTANT)
Q_PROPERTY(bool isReply MEMBER mIsReply 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 hasFileContent MEMBER mHasFileContent CONSTANT)
Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT) Q_PROPERTY(bool isVoiceRecording MEMBER mIsVoiceRecording CONSTANT)
Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT) Q_PROPERTY(bool isCalendarInvite MEMBER mIsCalendarInvite CONSTANT)
@ -123,6 +125,7 @@ public:
QString getFromAddress() const; QString getFromAddress() const;
QString getFromName() const; QString getFromName() const;
QString getToAddress() const; QString getToAddress() const;
QString getToName() const;
QString getMessageId() const; QString getMessageId() const;
bool isRemoteMessage() const; bool isRemoteMessage() const;
@ -182,6 +185,7 @@ private:
QString mFromAddress; QString mFromAddress;
QString mToAddress; QString mToAddress;
QString mFromName; QString mFromName;
QString mToName;
QString mPeerName; QString mPeerName;
QString mMessageId; QString mMessageId;
QString mOwnReaction; QString mOwnReaction;
@ -194,6 +198,8 @@ private:
bool mIsRead = false; bool mIsRead = false;
bool mIsForward = false; bool mIsForward = false;
bool mIsReply = false; bool mIsReply = false;
QString mReplyText;
QString mRepliedToName;
bool mHasFileContent = false; bool mHasFileContent = false;
bool mIsCalendarInvite = false; bool mIsCalendarInvite = false;
bool mIsVoiceRecording = false; bool mIsVoiceRecording = false;

View file

@ -43,11 +43,11 @@ EventLogCore::EventLogCore(const std::shared_ptr<const linphone::EventLog> &even
} else if (eventLog->getCallLog()) { } else if (eventLog->getCallLog()) {
mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog()); mCallHistoryCore = CallHistoryCore::create(eventLog->getCallLog());
mEventId = Utils::coreStringToAppString(eventLog->getCallLog()->getCallId()); mEventId = Utils::coreStringToAppString(eventLog->getCallLog()->getCallId());
} else { // getNotifyId }
if (mEventId.isEmpty()) { // getNotifyId
QString type = QString::fromLatin1( QString type = QString::fromLatin1(
QMetaEnum::fromType<LinphoneEnums::EventLogType>().valueToKey(static_cast<int>(mEventLogType))); QMetaEnum::fromType<LinphoneEnums::EventLogType>().valueToKey(static_cast<int>(mEventLogType)));
mEventId = type + QString::number(static_cast<qint64>(eventLog->getCreationTime())); mEventId = type + QString::number(static_cast<qint64>(eventLog->getCreationTime()));
;
computeEvent(eventLog); 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

View file

@ -137,6 +137,11 @@ ChatModel::createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder>
return mMonitor->createVoiceRecordingMessage(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) { std::shared_ptr<linphone::ChatMessage> ChatModel::createTextMessageFromText(QString text) {
return mMonitor->createMessageFromUtf8(Utils::appStringToCoreString(text)); return mMonitor->createMessageFromUtf8(Utils::appStringToCoreString(text));
} }

View file

@ -52,6 +52,9 @@ public:
void leave(); void leave();
std::shared_ptr<linphone::ChatMessage> std::shared_ptr<linphone::ChatMessage>
createVoiceRecordingMessage(const std::shared_ptr<linphone::Recorder> &recorder); 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> createTextMessageFromText(QString text);
std::shared_ptr<linphone::ChatMessage> createMessage(QString text, std::shared_ptr<linphone::ChatMessage> createMessage(QString text,
QList<std::shared_ptr<ChatMessageContentModel>> filesContent); QList<std::shared_ptr<ChatMessageContentModel>> filesContent);

View file

@ -388,11 +388,13 @@ bool ToolModel::friendIsInFriendList(const std::shared_ptr<linphone::FriendList>
QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) { QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Content>> contents) {
mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO)); mustBeInLinphoneThread(sLog().arg(Q_FUNC_INFO));
QString res;
for (auto &content : contents) { for (auto &content : contents) {
if (content->isText()) { if (content->isText()) {
return Utils::coreStringToAppString(content->getUtf8Text()); return Utils::coreStringToAppString(content->getUtf8Text());
} else if (content->isFile()) { } 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()) { } else if (content->isIcalendar()) {
auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content); auto conferenceInfo = linphone::Factory::get()->createConferenceInfoFromIcalendarContent(content);
auto conferenceInfoCore = ConferenceInfoCore::create(conferenceInfo); auto conferenceInfoCore = ConferenceInfoCore::create(conferenceInfo);
@ -406,7 +408,7 @@ QString ToolModel::getMessageFromContent(std::list<std::shared_ptr<linphone::Con
return getMessageFromContent(content->getParts()); 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 // Load downloaded codecs like OpenH264 (needs to be after core is created and has loaded its plugins, as

View file

@ -25,6 +25,7 @@
#include "core/call/CallGui.hpp" #include "core/call/CallGui.hpp"
#include "core/chat/ChatCore.hpp" #include "core/chat/ChatCore.hpp"
#include "core/chat/ChatGui.hpp" #include "core/chat/ChatGui.hpp"
#include "core/chat/message/ChatMessageGui.hpp"
#include "core/conference/ConferenceCore.hpp" #include "core/conference/ConferenceCore.hpp"
#include "core/conference/ConferenceInfoCore.hpp" #include "core/conference/ConferenceInfoCore.hpp"
#include "core/conference/ConferenceInfoGui.hpp" #include "core/conference/ConferenceInfoGui.hpp"
@ -1950,6 +1951,50 @@ QString Utils::getSafeFilePath(const QString &filePath, bool *soFarSoGood) {
return QString(""); 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 *Utils::createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui) {
VariantObject *data = new VariantObject("createVoiceRecordingMessage"); VariantObject *data = new VariantObject("createVoiceRecordingMessage");
if (!data) return nullptr; if (!data) return nullptr;

View file

@ -52,6 +52,7 @@ class ConferenceCore;
class ParticipantDeviceCore; class ParticipantDeviceCore;
class DownloadablePayloadTypeCore; class DownloadablePayloadTypeCore;
class ChatGui; class ChatGui;
class ChatMessageGui;
class RecorderGui; class RecorderGui;
class Utils : public QObject, public AbstractObject { 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 QString toTimeString(QDateTime date, const QString &format = "hh:mm:ss");
Q_INVOKABLE static VariantObject *createVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui); 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); Q_INVOKABLE static void sendVoiceRecordingMessage(RecorderGui *recorderGui, ChatGui *chatGui);
// QDir findDirectoryByName(QString startPath, QString name); // QDir findDirectoryByName(QString startPath, QString name);

View file

@ -19,6 +19,8 @@ Control.Control {
property string fromAddress: chatMessage? chatMessage.core.fromAddress : "" property string fromAddress: chatMessage? chatMessage.core.fromAddress : ""
property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false property bool isRemoteMessage: chatMessage? chatMessage.core.isRemoteMessage : false
property bool isFromChatGroup: chatMessage? chatMessage.core.isFromChatGroup : 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 property var msgState: chatMessage ? chatMessage.core.messageState : LinphoneEnums.ChatMessageState.StateIdle
hoverEnabled: true hoverEnabled: true
property bool linkHovered: false property bool linkHovered: false
@ -30,6 +32,7 @@ Control.Control {
signal isFileHoveringChanged(bool isFileHovering) signal isFileHoveringChanged(bool isFileHovering)
signal showReactionsForMessageRequested() signal showReactionsForMessageRequested()
signal showImdnStatusForMessageRequested() signal showImdnStatusForMessageRequested()
signal replyToMessageRequested()
background: Item { background: Item {
anchors.fill: parent anchors.fill: parent
@ -41,9 +44,95 @@ Control.Control {
} }
} }
contentItem: RowLayout { contentItem: ColumnLayout {
spacing: Math.round(5 * DefaultStyle.dp)
Text {
id: fromNameText
Layout.alignment: Qt.AlignTop
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
}
}
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 spacing: 0
layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft layoutDirection: mainItem.isRemoteMessage ? Qt.LeftToRight : Qt.RightToLeft
Layout.topMargin: replyMessage.visible ? Math.round(-20 * DefaultStyle.dp) : 0
Avatar { Avatar {
id: avatar id: avatar
@ -54,39 +143,20 @@ Control.Control {
Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0 Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
_address: chatMessage ? chatMessage.core.fromAddress : "" _address: chatMessage ? chatMessage.core.fromAddress : ""
} }
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
}
}
Item { Item {
id: bubbleContainer
// Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0 // Layout.topMargin: isFirstMessage ? 16 * DefaultStyle.dp : 0
Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0 Layout.leftMargin: mainItem.isFromChatGroup ? Math.round(9 * DefaultStyle.dp) : 0
Layout.preferredHeight: childrenRect.height Layout.preferredHeight: childrenRect.height
Layout.preferredWidth: childrenRect.width Layout.preferredWidth: childrenRect.width
Control.Control { Control.Control {
id: chatBubble id: chatBubble
spacing: Math.round(2 * DefaultStyle.dp) spacing: Math.round(2 * DefaultStyle.dp)
topPadding: Math.round(12 * DefaultStyle.dp) topPadding: Math.round(12 * DefaultStyle.dp)
bottomPadding: Math.round(6 * DefaultStyle.dp) bottomPadding: Math.round(6 * DefaultStyle.dp)
leftPadding: Math.round(12 * DefaultStyle.dp) leftPadding: Math.round(18 * DefaultStyle.dp)
rightPadding: Math.round(12 * DefaultStyle.dp) rightPadding: Math.round(18 * DefaultStyle.dp)
width: Math.min(implicitWidth, mainItem.maxWidth - avatar.implicitWidth) 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. 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 { RowLayout {
Layout.fillWidth: false
Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight Layout.alignment: mainItem.isRemoteMessage ? Qt.AlignLeft : Qt.AlignRight
Text { Text {
text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM") text: UtilsCpp.formatDate(mainItem.chatMessage.core.timestamp, true, false, "dd/MM")
@ -217,7 +288,6 @@ Control.Control {
} }
} }
} }
}
RowLayout { RowLayout {
id: actionsLayout id: actionsLayout
visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered || emojiButton.popup.opened visible: mainItem.hovered || optionsMenu.hovered || optionsMenu.popup.opened || emojiButton.hovered || emojiButton.popup.opened
@ -232,6 +302,30 @@ Control.Control {
popup.padding: 0 popup.padding: 0
popup.contentItem: ColumnLayout { popup.contentItem: ColumnLayout {
spacing: 0 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: {
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 { IconLabelButton {
inverseLayout: true inverseLayout: true
text: chatBubbleContent.selectedText != "" text: chatBubbleContent.selectedText != ""
@ -252,18 +346,6 @@ Control.Control {
optionsMenu.close() 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 { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(1, Math.round(1 * DefaultStyle.dp)) Layout.preferredHeight: Math.min(1, Math.round(1 * DefaultStyle.dp))
@ -331,4 +413,5 @@ Control.Control {
} }
Item{Layout.fillWidth: true} Item{Layout.fillWidth: true}
} }
}
} }

View file

@ -16,6 +16,7 @@ ListView {
property color backgroundColor property color backgroundColor
signal showReactionsForMessageRequested(ChatMessageGui chatMessage) signal showReactionsForMessageRequested(ChatMessageGui chatMessage)
signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage) signal showImdnStatusForMessageRequested(ChatMessageGui chatMessage)
signal replyToMessageRequested(ChatMessageGui chatMessage)
Component.onCompleted: { Component.onCompleted: {
var index = eventLogProxy.findFirstUnreadIndex() var index = eventLogProxy.findFirstUnreadIndex()
@ -137,6 +138,7 @@ ListView {
onMessageDeletionRequested: modelData.core.lDelete() onMessageDeletionRequested: modelData.core.lDelete()
onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(modelData) onShowReactionsForMessageRequested: mainItem.showReactionsForMessageRequested(modelData)
onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(modelData) onShowImdnStatusForMessageRequested: mainItem.showImdnStatusForMessageRequested(modelData)
onReplyToMessageRequested: mainItem.replyToMessageRequested(modelData)
} }
} }

View file

@ -37,7 +37,6 @@ Control.Control {
function _emitFiles (files) { function _emitFiles (files) {
// Filtering files, other urls are forbidden. // Filtering files, other urls are forbidden.
files = files.reduce(function (files, file) { files = files.reduce(function (files, file) {
console.log("dropping", file.toString())
if (file.toString().startsWith("file:")) { if (file.toString().startsWith("file:")) {
files.push(Utils.getSystemPathFromUri(file)) files.push(Utils.getSystemPathFromUri(file))
} }

View file

@ -18,6 +18,7 @@ RowLayout {
property var contact: contactObj?.value || null property var contact: contactObj?.value || null
property CallGui call property CallGui call
property alias callHeaderContent: splitPanel.headerContent property alias callHeaderContent: splitPanel.headerContent
property bool replyingToMessage: false
spacing: 0 spacing: 0
signal oneOneCall(bool video) signal oneOneCall(bool video)
@ -161,6 +162,10 @@ RowLayout {
contentLoader.showingImdnStatus = true contentLoader.showingImdnStatus = true
detailsPanel.visible = true detailsPanel.visible = true
} }
onReplyToMessageRequested: (chatMessage) => {
mainItem.chatMessage = chatMessage
mainItem.replyingToMessage = true
}
Popup { Popup {
id: emojiPickerPopup id: emojiPickerPopup
@ -208,9 +213,9 @@ RowLayout {
} }
Control.Control { Control.Control {
id: selectedFilesArea id: selectedFilesArea
visible: selectedFiles.count > 0 visible: selectedFiles.count > 0 || mainItem.replyingToMessage
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp) Layout.preferredHeight: implicitHeight
topPadding: Math.round(12 * DefaultStyle.dp) topPadding: Math.round(12 * DefaultStyle.dp)
bottomPadding: Math.round(12 * DefaultStyle.dp) bottomPadding: Math.round(12 * DefaultStyle.dp)
leftPadding: Math.round(19 * DefaultStyle.dp) leftPadding: Math.round(19 * DefaultStyle.dp)
@ -225,6 +230,7 @@ RowLayout {
style: ButtonStyle.noBackground style: ButtonStyle.noBackground
onClicked: { onClicked: {
contents.clear() contents.clear()
mainItem.replyingToMessage = false
} }
} }
background: Item{ background: Item{
@ -233,7 +239,6 @@ RowLayout {
color: DefaultStyle.grey_0 color: DefaultStyle.grey_0
border.color: DefaultStyle.main2_100 border.color: DefaultStyle.main2_100
border.width: Math.round(2 * DefaultStyle.dp) border.width: Math.round(2 * DefaultStyle.dp)
radius: Math.round(20 * DefaultStyle.dp)
height: parent.height / 2 height: parent.height / 2
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
@ -246,10 +251,45 @@ RowLayout {
height: 2 * parent.height / 3 height: 2 * parent.height / 3
} }
} }
contentItem: ListView { 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
}
}
Text {
Layout.fillWidth: true
text: mainItem.chatMessage ? mainItem.chatMessage.core.text : ""
color: DefaultStyle.main2_400
font {
pixelSize: Typography.p3.pixelSize
weight: Typography.p3.weight
}
}
}
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 id: selectedFiles
orientation: ListView.Horizontal orientation: ListView.Horizontal
visible: count > 0
spacing: Math.round(16 * DefaultStyle.dp) spacing: Math.round(16 * DefaultStyle.dp)
Layout.fillWidth: true
Layout.preferredHeight: Math.round(104 * DefaultStyle.dp)
model: ChatMessageContentProxy { model: ChatMessageContentProxy {
id: contents id: contents
filterType: ChatMessageContentProxy.FilterContentType.File filterType: ChatMessageContentProxy.FilterContentType.File
@ -278,6 +318,7 @@ RowLayout {
} }
Control.ScrollBar.horizontal: selectedFilesScrollbar Control.ScrollBar.horizontal: selectedFilesScrollbar
} }
}
ScrollBar { ScrollBar {
id: selectedFilesScrollbar id: selectedFilesScrollbar
active: true active: true
@ -304,7 +345,11 @@ RowLayout {
} }
onSendMessage: { onSendMessage: {
var filesContents = contents.getAll() 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) mainItem.chat.core.lSendTextMessage(text)
else mainItem.chat.core.lSendMessage(text, filesContents) else mainItem.chat.core.lSendMessage(text, filesContents)
contents.clear() contents.clear()