send message with files

This commit is contained in:
Gaelle Braud 2025-06-06 14:26:13 +02:00
parent 9d5935fb53
commit f69c5c3703
20 changed files with 1588 additions and 1064 deletions

View file

@ -163,7 +163,7 @@ add_option(OPTION_LIST ENABLE_FFMPEG "Build mediastreamer2 with ffmpeg video sup
add_option(OPTION_LIST ENABLE_LDAP "Enable LDAP support." YES)
add_option(OPTION_LIST ENABLE_NON_FREE_CODECS "Enable the use of non free codecs" YES)
add_option(OPTION_LIST ENABLE_NON_FREE_FEATURES "Enable the use of non free codecs" ${ENABLE_NON_FREE_CODECS})
add_option(OPTION_LIST ENABLE_QT_KEYCHAIN "Build QtKeychain to manage VFS from System key stores." ON)
add_option(OPTION_LIST ENABLE_QT_KEYCHAIN "Build QtKeychain to manage VFS from System key stores." OFF)
add_option(OPTION_LIST ENABLE_QRCODE "Enable QRCode support" OFF)#Experimental
add_option(OPTION_LIST ENABLE_RELATIVE_PREFIX "Set Internal packages relative to the binary" ON)
add_option(OPTION_LIST ENABLE_SANITIZER "Enable sanitizer." OFF)

View file

@ -20,6 +20,7 @@
#include "ChatCore.hpp"
#include "core/App.hpp"
#include "core/chat/message/content/ChatMessageContentGui.hpp"
#include "core/friend/FriendCore.hpp"
#include "core/setting/SettingsCore.hpp"
#include "model/tool/ToolModel.hpp"
@ -208,6 +209,21 @@ void ChatCore::setSelf(QSharedPointer<ChatCore> me) {
linMessage->send();
});
});
mChatModelConnection->makeConnectToCore(&ChatCore::lSendMessage, [this](QString message, QVariantList files) {
if (Utils::isEmptyMessage(message) && files.size() == 0) 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());
}
}
mChatModelConnection->invokeToModel([this, message, filesContent]() {
auto linMessage = mChatModel->createMessage(message, filesContent);
linMessage->send();
});
});
mChatModelConnection->makeConnectToModel(
&ChatModel::chatMessageSending, [this](const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<const linphone::EventLog> &eventLog) {

View file

@ -147,6 +147,7 @@ signals:
void lUpdateUnreadCount();
void lUpdateLastUpdatedTime();
void lSendTextMessage(QString message);
void lSendMessage(QString message, QVariantList files);
void lCompose();
void lLeave();
void lSetMuted(bool muted);

View file

@ -81,6 +81,117 @@ int ChatMessageContentList::findFirstUnreadIndex() {
return it == chatList.end() ? -1 : std::distance(chatList.begin(), it);
}
void ChatMessageContentList::addFiles(const QStringList &paths) {
mustBeInMainThread(log().arg(Q_FUNC_INFO));
QStringList finalList;
QList<QFileInfo> fileList;
int nbNotFound = 0;
QString lastNotFound;
int nbExcess = 0;
int count = rowCount();
for (auto &path : paths) {
QFileInfo file(path.toUtf8());
// #ifdef _WIN32
// // A bug from FileDialog suppose that the file is local and overwrite the uri by removing "\\".
// if (!file.exists()) {
// path.prepend("\\\\");
// file.setFileName(path);
// }
// #endif
if (!file.exists()) {
++nbNotFound;
lastNotFound = path;
continue;
}
if (count + finalList.count() >= 12) {
++nbExcess;
continue;
}
finalList.append(path);
fileList.append(file);
}
if (nbNotFound > 0) {
//: Error adding file
Utils::showInformationPopup(tr("popup_error_title"),
//: File was not found: %1
(nbNotFound == 1 ? tr("popup_error_path_does_not_exist_message").arg(lastNotFound)
//: %n files were not found
: tr("popup_error_nb_files_not_found_message").arg(nbNotFound)),
false);
}
if (nbExcess > 0) {
//: Error
Utils::showInformationPopup(tr("popup_error_title"),
//: You can send 12 files maximum at a time. %n files were ignored
tr("popup_error_max_files_count_message", "", nbExcess), false);
}
mModelConnection->invokeToModel([this, finalList, fileList] {
int nbTooBig = 0;
int nbMimeError = 0;
QString lastMimeError;
QList<QSharedPointer<ChatMessageContentCore>> contentList;
for (auto &file : fileList) {
qint64 fileSize = file.size();
if (fileSize > Constants::FileSizeLimit) {
++nbTooBig;
qWarning() << QString("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
continue;
}
auto name = file.fileName().toStdString();
auto path = file.filePath();
std::shared_ptr<linphone::Content> content = CoreModel::getInstance()->getCore()->createContent();
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
if (mimeType.length() != 2) {
++nbMimeError;
lastMimeError = path;
qWarning() << QString("Unable to get supported mime type for: `%1`.").arg(path);
continue;
}
content->setType(Utils::appStringToCoreString(mimeType[0]));
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
content->setSize(size_t(fileSize));
content->setName(name);
content->setFilePath(Utils::appStringToCoreString(path));
contentList.append(ChatMessageContentCore::create(content, nullptr));
}
if (nbTooBig > 0) {
//: Error adding file
Utils::showInformationPopup(
tr("popup_error_title"),
//: %n files were ignored cause they exceed the maximum size. (Size limit=%2)
tr("popup_error_file_too_big_message").arg(nbTooBig).arg(Constants::FileSizeLimit), false);
}
if (nbMimeError > 0) {
//: Error adding file
Utils::showInformationPopup(tr("popup_error_title"),
//: Unable to get supported mime type for: `%1`.
(nbMimeError == 1
? tr("popup_error_unsupported_file_message").arg(lastMimeError)
//: Unable to get supported mime type for %1 files.
: tr("popup_error_unsupported_files_message").arg(nbMimeError)),
false);
}
mModelConnection->invokeToCore([this, contentList] {
for (auto &contentCore : contentList) {
connect(contentCore.get(), &ChatMessageContentCore::isFileChanged, this, [this, contentCore] {
int i = -1;
get(contentCore.get(), &i);
emit dataChanged(index(i), index(i));
});
add(contentCore);
contentCore->lCreateThumbnail(
true); // Was not created because linphone::Content is not considered as a file (yet)
}
});
});
}
void ChatMessageContentList::setSelf(QSharedPointer<ChatMessageContentList> me) {
mModelConnection = SafeConnection<ChatMessageContentList, CoreModel>::create(me, CoreModel::getInstance());
@ -101,57 +212,8 @@ void ChatMessageContentList::setSelf(QSharedPointer<ChatMessageContentList> me)
}
resetData<ChatMessageContentCore>(contents);
});
mModelConnection->makeConnectToCore(&ChatMessageContentList::lAddFile, [this](const QString &path) {
QFile file(path);
// #ifdef _WIN32
// // A bug from FileDialog suppose that the file is local and overwrite the uri by removing "\\".
// if (!file.exists()) {
// path.prepend("\\\\");
// file.setFileName(path);
// }
// #endif
if (!file.exists()) return;
if (rowCount() >= 12) {
//: Error
Utils::showInformationPopup(tr("popup_error_title"),
//: You can add 12 files maximum
tr("popup_error_max_files_count_message"), false);
return;
}
qint64 fileSize = file.size();
if (fileSize > Constants::FileSizeLimit) {
qWarning() << QStringLiteral("Unable to send file. (Size limit=%1)").arg(Constants::FileSizeLimit);
return;
}
auto name = QFileInfo(file).fileName().toStdString();
mModelConnection->invokeToModel([this, path, fileSize, name] {
std::shared_ptr<linphone::Content> content = CoreModel::getInstance()->getCore()->createContent();
{
QStringList mimeType = QMimeDatabase().mimeTypeForFile(path).name().split('/');
if (mimeType.length() != 2) {
qWarning() << QStringLiteral("Unable to get supported mime type for: `%1`.").arg(path);
return;
}
content->setType(Utils::appStringToCoreString(mimeType[0]));
content->setSubtype(Utils::appStringToCoreString(mimeType[1]));
}
content->setSize(size_t(fileSize));
content->setName(name);
content->setFilePath(Utils::appStringToCoreString(path));
auto contentCore = ChatMessageContentCore::create(content, nullptr);
mModelConnection->invokeToCore([this, contentCore] {
connect(contentCore.get(), &ChatMessageContentCore::isFileChanged, this, [this, contentCore] {
int i = -1;
get(contentCore.get(), &i);
emit dataChanged(index(i), index(i));
});
add(contentCore);
contentCore->lCreateThumbnail(
true); // Was not created because linphone::Content is not considered as a file (yet)
});
});
});
mModelConnection->makeConnectToCore(&ChatMessageContentList::lAddFiles,
[this](const QStringList &paths) { addFiles(paths); });
emit lUpdate();
}

View file

@ -44,11 +44,13 @@ public:
int findFirstUnreadIndex();
void addFiles(const QStringList &paths);
void setSelf(QSharedPointer<ChatMessageContentList> me);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
signals:
void lAddFile(QString path);
void lAddFiles(QStringList paths);
void isFileChanged();
void lUpdate();
void chatMessageChanged();

View file

@ -68,9 +68,9 @@ void ChatMessageContentProxy::setChatMessageGui(ChatMessageGui *chat) {
// return nullptr;
// }
void ChatMessageContentProxy::addFile(const QString &path) {
void ChatMessageContentProxy::addFiles(const QStringList &paths) {
auto model = getListModel<ChatMessageContentList>();
if (model) emit model->lAddFile(path.toUtf8());
if (model) emit model->lAddFiles(paths);
}
void ChatMessageContentProxy::removeContent(ChatMessageContentGui *contentGui) {

View file

@ -47,7 +47,7 @@ public:
void setSourceModel(QAbstractItemModel *sourceModel) override;
Q_INVOKABLE void addFile(const QString &path);
Q_INVOKABLE void addFiles(const QStringList &paths);
Q_INVOKABLE void removeContent(ChatMessageContentGui *contentGui);
Q_INVOKABLE void clear();

View file

@ -71,6 +71,15 @@ QVariant LimitProxy::getAt(const int &atIndex) const {
return sourceModel()->data(mapToSource(modelIndex), 0);
}
QVariantList LimitProxy::getAll() const {
QVariantList ret;
for (int i = 0; i < getCount(); ++i) {
auto modelIndex = index(i, 0);
if (modelIndex.isValid()) ret.append(sourceModel()->data(mapToSource(modelIndex), 0));
}
return ret;
}
int LimitProxy::getCount() const {
return rowCount();
}

View file

@ -47,6 +47,7 @@ public:
Q_INVOKABLE void displayMore();
Q_INVOKABLE QVariant getAt(const int &index) const;
Q_INVOKABLE QVariantList getAll() const;
virtual int getCount() const;
// Get the item following by what is shown from 2 lists

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

@ -132,6 +132,16 @@ std::shared_ptr<linphone::ChatMessage> ChatModel::createTextMessageFromText(QStr
return mMonitor->createMessageFromUtf8(Utils::appStringToCoreString(text));
}
std::shared_ptr<linphone::ChatMessage>
ChatModel::createMessage(QString text, QList<std::shared_ptr<ChatMessageContentModel>> filesContent) {
auto message = mMonitor->createEmptyMessage();
for (auto &content : filesContent) {
message->addFileContent(content->getContent());
}
if (!text.isEmpty()) message->addUtf8TextContent(Utils::appStringToCoreString(text));
return message;
}
void ChatModel::compose() {
mMonitor->compose();
}

View file

@ -21,6 +21,7 @@
#ifndef CHAT_MODEL_H_
#define CHAT_MODEL_H_
#include "model/chat/message/content/ChatMessageContentModel.hpp"
#include "model/listener/Listener.hpp"
#include "tool/AbstractObject.hpp"
#include "tool/LinphoneEnums.hpp"
@ -49,12 +50,13 @@ public:
void deleteChatRoom();
void leave();
std::shared_ptr<linphone::ChatMessage> createTextMessageFromText(QString text);
std::shared_ptr<linphone::ChatMessage> createMessage(QString text,
QList<std::shared_ptr<ChatMessageContentModel>> filesContent);
void compose();
linphone::ChatRoom::State getState() const;
void setMuted(bool muted);
void enableEphemeral(bool enable);
signals:
void historyDeleted();
void messagesRead();

View file

@ -110,7 +110,11 @@ QImage ThumbnailAsyncImageResponse::createThumbnail(const QString &path, QImage
else if (rotation == 7 || rotation == 8) transform.rotate(-90);
thumbnail = thumbnail.transformed(transform);
if (rotation == 2 || rotation == 4 || rotation == 5 || rotation == 7)
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
thumbnail = thumbnail.mirrored(true, false);
#else
thumbnail = thumbnail.flipped(Qt::Horizontal);
#endif
}
}
return thumbnail;

View file

@ -47,7 +47,6 @@ ListView {
mainItem.currentIndex = indexToSelect
}
onLayoutChanged: {
var chatToSelect = getAt(mainItem.currentIndex)
selectChat(mainItem.currentChatGui)
}
}

View file

@ -39,24 +39,28 @@ Item {
property bool isOutgoing: false
MouseArea {
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
// Changing of cursor seems not to work with the Loader
// Use override cursor for this case
function handleMouseMove (mouse) {
thumbnailProvider.state = Utils.pointIsInItem(this, thumbnailProvider, mouse)
? 'hovered'
: ''
}
onMouseXChanged: (mouse) => handleMouseMove.call(this, mouse)
onMouseYChanged: (mouse) => handleMouseMove.call(this, mouse)
onEntered: {
UtilsCpp.setGlobalCursor(Qt.PointingHandCursor)
thumbnailProvider.state = 'hovered'
}
onExited: {
UtilsCpp.restoreGlobalCursor()
thumbnailProvider.state = ''
}
anchors.fill: parent
onClicked: (mouse) => {
mouse.accepted = false
if(mainItem.isTransferring) {
mainItem.contentGui.core.lCancelDownloadFile()
mouse.accepted = true
thumbnailProvider.state = ''
}
else if(!mainItem.contentGui.core.wasDownloaded) {
mouse.accepted = true
@ -64,6 +68,7 @@ Item {
mainItem.contentGui.core.lDownloadFile()
} else if (Utils.pointIsInItem(this, thumbnailProvider, mouse)) {
mouse.accepted = true
thumbnailProvider.state = ''
// if(SettingsModel.isVfsEncrypted){
// window.attachVirtualWindow(Utils.buildCommonDialogUri('FileViewDialog'), {
// contentGui: mainItem.contentGui,
@ -290,5 +295,11 @@ Item {
states: State {
name: 'hovered'
}
// Changing cursor in MouseArea seems not to work with the Loader
// Use override cursor for this case
onStateChanged: {
if (state === 'hovered') UtilsCpp.setGlobalCursor(Qt.PointingHandCursor)
else UtilsCpp.restoreGlobalCursor()
}
}
}

View file

@ -1,5 +1,6 @@
import QtQuick
import QtQuick.Controls.Basic as Control
import QtQuick.Dialogs
import QtQuick.Layouts
import Linphone
import UtilsCpp
@ -47,6 +48,12 @@ Control.Control {
}
}
FileDialog {
id: fileDialog
fileMode: FileDialog.OpenFiles
onAccepted: _emitFiles(fileDialog.selectedFiles)
}
// width: mainItem.implicitWidth
// height: mainItem.height
leftPadding: Math.round(15 * DefaultStyle.dp)
@ -80,7 +87,7 @@ Control.Control {
style: ButtonStyle.noBackground
icon.source: AppIcons.paperclip
onClicked: {
console.log("TODO : open explorer to attach file")
fileDialog.open()
}
}
Control.Control {
@ -144,10 +151,9 @@ Control.Control {
onCursorRectangleChanged: sendingAreaFlickable.ensureVisible(cursorRectangle)
wrapMode: TextEdit.WordWrap
Keys.onPressed: (event) => {
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return)
&& (!(event.modifier & Qt.ShiftModifier))) {
if ((event.key == Qt.Key_Enter || event.key == Qt.Key_Return))
if(!(event.modifiers & Qt.ShiftModifier)) {
mainItem.sendText()
sendingTextArea.clear()
event.accepted = true
}
}
@ -168,7 +174,6 @@ Control.Control {
icon.source: AppIcons.paperPlaneRight
onClicked: {
mainItem.sendText()
sendingTextArea.clear()
}
}
}

View file

@ -147,10 +147,6 @@ RowLayout {
bottomPadding: Math.round(12 * DefaultStyle.dp)
leftPadding: Math.round(19 * DefaultStyle.dp)
rightPadding: Math.round(19 * DefaultStyle.dp)
function addFile(path) {
contents.addFile(path)
}
Button {
anchors.top: parent.top
@ -212,6 +208,14 @@ RowLayout {
onClicked: contents.removeContent(modelData)
}
}
Control.ScrollBar.horizontal: selectedFilesScrollbar
}
ScrollBar {
id: selectedFilesScrollbar
active: true
anchors.bottom: selectedFilesArea.bottom
anchors.left: selectedFilesArea.left
anchors.right: selectedFilesArea.right
}
}
ChatDroppableTextArea {
@ -228,9 +232,16 @@ RowLayout {
}
mainItem.chat.core.sendingText = text
}
onSendText: mainItem.chat.core.lSendTextMessage(text)
onSendText: {
var filesContents = contents.getAll()
if (filesContents.length === 0)
mainItem.chat.core.lSendTextMessage(text)
else mainItem.chat.core.lSendMessage(text, filesContents)
messageSender.textArea.clear()
contents.clear()
}
onDropped: (files) => {
files.forEach(selectedFilesArea.addFile)
contents.addFiles(files)
}
}
}

View file

@ -1,8 +1,6 @@
add_subdirectory(linphone-sdk/)
if(ENABLE_QT_KEYCHAIN)
function(add_linphone_keychain)
add_subdirectory(qtkeychain/)
endfunction()
add_linphone_keychain()
find_package(Qt6 REQUIRED COMPONENTS Test)
add_subdirectory(qtkeychain/)
endif()