diff --git a/data/apirequest.cpp b/data/apirequest.cpp index 48ab61f..34b4453 100644 --- a/data/apirequest.cpp +++ b/data/apirequest.cpp @@ -1,4 +1,5 @@ #include +#include #include "apirequest.h" @@ -38,6 +39,31 @@ void APIRequest::addBool(QString name, bool value) mArguments.append(APIRequestParameter(name, value ? "true" : "false")); } +void APIRequest::addFileFromPath(const QString &name, const QString &path, const QString &fileType) +{ + //Determine files name for the request + QString partName = name; + partName.replace("\"", "\\\""); + QString fileName = ("/"+path).split("/").last(); + fileName.replace("\"", "\\\""); + + QHttpPart part; + part.setHeader(QNetworkRequest::ContentTypeHeader, fileType); + part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\""+partName+"\"; filename=\""+fileName+"\"")); + + QFile *file = new QFile(path); + if(!file->open(QIODevice::ReadOnly)){ + qWarning("Could not open file to send: %s !", path.toStdString().c_str()); + return; + } + + part.setBodyDevice(file); + mParts.append(part); + + //Automatically delete file object once request is completed + file->setParent(this); +} + QList APIRequest::arguments() const { return mArguments; @@ -52,3 +78,13 @@ void APIRequest::setNetworkReply(QNetworkReply *networkReply) { mNetworkReply = networkReply; } + +QList *APIRequest::parts() +{ + return &mParts; +} + +bool APIRequest::hasParts() const +{ + return mParts.size() > 0; +} diff --git a/data/apirequest.h b/data/apirequest.h index 8ea94e9..b04c858 100644 --- a/data/apirequest.h +++ b/data/apirequest.h @@ -10,6 +10,7 @@ #include #include +#include #include "apirequestparameter.h" @@ -50,6 +51,15 @@ public: */ void addBool(QString name, bool value); + /** + * Add a file from filesystem path + * + * @param name The name of the file to add + * @param path The path to the file + * @param fileType The type of the file to add + */ + void addFileFromPath(const QString &name, const QString &path, const QString &fileType); + /** * Get the entire list of arguments of the request * @@ -57,6 +67,14 @@ public: */ QList arguments() const; + /** + * Get the list of HTTP parts included with this request + * + * @return Pointer on the list containing the list of parts + */ + QList *parts(); + bool hasParts() const; + //Get and set network reply associated with the request QNetworkReply *networkReply() const; void setNetworkReply(QNetworkReply *networkReply); @@ -91,6 +109,7 @@ public slots: private: QString mURI; QList mArguments; + QList mParts; QNetworkReply *mNetworkReply = nullptr; }; diff --git a/data/newconversationmessage.cpp b/data/newconversationmessage.cpp index ee81c1c..1d5edbe 100644 --- a/data/newconversationmessage.cpp +++ b/data/newconversationmessage.cpp @@ -24,3 +24,18 @@ void NewConversationMessage::setMessage(const QString &message) { mMessage = message; } + +QString NewConversationMessage::imagePath() const +{ + return mImagePath; +} + +bool NewConversationMessage::hasImage() const +{ + return !mImagePath.isEmpty(); +} + +void NewConversationMessage::setImagePath(const QString &imagePath) +{ + mImagePath = imagePath; +} diff --git a/data/newconversationmessage.h b/data/newconversationmessage.h index fd6e530..ab0f07d 100644 --- a/data/newconversationmessage.h +++ b/data/newconversationmessage.h @@ -22,9 +22,14 @@ public: QString message() const; void setMessage(const QString &message); + QString imagePath() const; + bool hasImage() const; + void setImagePath(const QString &imagePath); + private: int mIDConversation; QString mMessage; + QString mImagePath; }; #endif // NEWCONVERSATIONMESSAGE_H diff --git a/helpers/apihelper.cpp b/helpers/apihelper.cpp index f728124..de28d01 100644 --- a/helpers/apihelper.cpp +++ b/helpers/apihelper.cpp @@ -30,16 +30,49 @@ void APIHelper::execute(APIRequest *request) request->addString("userToken2", tokens.token2()); } - //Prepare request - //See this SO question to learn more : https://stackoverflow.com/questions/2599423 - QUrlQuery queryData; - for(APIRequestParameter param : request->arguments()) - queryData.addQueryItem(param.name(), param.value()); + QNetworkReply *reply = nullptr; - //Send request - QNetworkRequest networkRequest((QUrl(requestURL))); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QNetworkReply *reply = mNetworkManager.post(networkRequest, queryData.toString(QUrl::FullyEncoded).toUtf8()); + //Prepare request + //Check if the request contains files or not + if(!request->hasParts()){ + + //See this SO question to learn more : https://stackoverflow.com/questions/2599423 + QUrlQuery queryData; + for(APIRequestParameter param : request->arguments()) + queryData.addQueryItem(param.name(), param.value()); + + //Send request + QNetworkRequest networkRequest((QUrl(requestURL))); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + reply = mNetworkManager.post(networkRequest, queryData.toString(QUrl::FullyEncoded).toUtf8()); + + } + + //Multiple entries request + else { + + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + //Process the list of "normal" arguments + for(APIRequestParameter param : request->arguments()){ + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\""+param.name()+"\""); + part.setBody(param.value().toStdString().c_str()); + multiPart->append(part); + } + + //Append all the other parts + for(int i = 0; i < request->parts()->size(); i++) + multiPart->append(request->parts()->at(i)); + + //Send request + QNetworkRequest networkRequest((QUrl(requestURL))); + reply = mNetworkManager.post(networkRequest, multiPart); + + //Delete multipart as soon as the request ends + multiPart->setParent(reply); + + } //Make connections connect(reply, &QNetworkReply::finished, this, &APIHelper::finished); diff --git a/helpers/conversationhelper.cpp b/helpers/conversationhelper.cpp index 942c101..8a645fd 100644 --- a/helpers/conversationhelper.cpp +++ b/helpers/conversationhelper.cpp @@ -4,6 +4,7 @@ #include "apihelper.h" #include "conversationhelper.h" #include "../data/apirequest.h" +#include "../utils/filesutils.h" ConversationHelper::ConversationHelper(QObject *parent) : QObject(parent) { @@ -17,6 +18,14 @@ void ConversationHelper::sendMessage(const NewConversationMessage &message) request->addInt("conversationID", message.iDConversation()); request->addString("message", message.message()); + //Add image (if any) + if(message.hasImage()){ + + //Add image to request + request->addFileFromPath("image", message.imagePath(), FilesUtils::GetFileMimeType(message.imagePath())); + + } + connect(request, &APIRequest::finished, this, &ConversationHelper::sendMessageFinished); mAPIHelper->execute(request); } diff --git a/res/baseline_insert_photo_black_48dp.png b/res/baseline_insert_photo_black_48dp.png new file mode 100755 index 0000000..49d6eae Binary files /dev/null and b/res/baseline_insert_photo_black_48dp.png differ diff --git a/res/ressources.qrc b/res/ressources.qrc index 1f6b944..9c1d94c 100644 --- a/res/ressources.qrc +++ b/res/ressources.qrc @@ -3,5 +3,6 @@ baseline_people_black_48dp.png baseline_access_time_black_48dp.png baseline_person_black_48dp.png + baseline_insert_photo_black_48dp.png diff --git a/utils/filesutils.cpp b/utils/filesutils.cpp index 2cb3a61..ee73088 100644 --- a/utils/filesutils.cpp +++ b/utils/filesutils.cpp @@ -1,4 +1,5 @@ #include +#include #include "filesutils.h" @@ -17,3 +18,8 @@ bool FilesUtils::CreateDirectoryIfNotExists(const QString &path) return dir.mkpath("."); } + +QString FilesUtils::GetFileMimeType(const QString &filePath) +{ + return (QMimeDatabase()).mimeTypeForFile(filePath).name(); +} diff --git a/utils/filesutils.h b/utils/filesutils.h index 307bab0..6113a96 100644 --- a/utils/filesutils.h +++ b/utils/filesutils.h @@ -23,6 +23,14 @@ public: * FALSE else */ static bool CreateDirectoryIfNotExists(const QString &path); + + /** + * Get the mime type of a file + * + * @param filePath The path of the file to determine + * @return File type + */ + static QString GetFileMimeType(const QString &filePath); }; #endif // FILESUTILS_H diff --git a/widgets/conversationwidget.cpp b/widgets/conversationwidget.cpp index 1f35f0b..c24fafa 100644 --- a/widgets/conversationwidget.cpp +++ b/widgets/conversationwidget.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "conversationwidget.h" #include "ui_conversationwidget.h" @@ -40,6 +41,31 @@ ConversationWidget::~ConversationWidget() delete mTimer; } +void ConversationWidget::setMessageFormImage() +{ + //Check if an image has already been selected by the user + if(hasUserSelectedImageToSend()){ + + //Ask user confirmation + if(QMessageBox::question( + this, + tr("Unselect image"), + tr("Are you sure to remove currently selected image from message ?") + ) != QMessageBox::Yes) + return; + + mPathToCurrentImageInForm = ""; + } + + //Pick an image + else + mPathToCurrentImageInForm = QFileDialog::getOpenFileName(this, + tr("Choose image to include in the message"), "", tr("Image Files (*.png *.jpg *.jpeg, *.gif)")); + + //Check if we have an image selected + refreshPickImageButton(); +} + void ConversationWidget::sendMessage() { if(isSendMessageFormLocked()){ @@ -50,7 +76,7 @@ void ConversationWidget::sendMessage() QString content = ui->messageContentInput->text(); //Check message length - if(content.length() < CONVERSATION_MESSAGE_MIN_LENGTH){ + if(content.length() < CONVERSATION_MESSAGE_MIN_LENGTH && !hasUserSelectedImageToSend()){ QMessageBox::warning(this, tr("Invalid message!"), tr("Specified message is too short!")); return; } @@ -63,6 +89,10 @@ void ConversationWidget::sendMessage() newMessage.setIDConversation(mConversation.iD()); newMessage.setMessage(content); + //Include user image (if any) + if(hasUserSelectedImageToSend()) + newMessage.setImagePath(mPathToCurrentImageInForm); + //Request the message to be sent mConversationHelper->sendMessage(newMessage); } @@ -135,6 +165,7 @@ void ConversationWidget::setSendMessageFormLocked(bool lock) { ui->sendMessageButton->setEnabled(!lock); ui->messageContentInput->setEnabled(!lock); + ui->addImageButton->setEnabled(!lock); } bool ConversationWidget::isSendMessageFormLocked() @@ -145,4 +176,21 @@ bool ConversationWidget::isSendMessageFormLocked() void ConversationWidget::resetSendMessageForm() { ui->messageContentInput->setText(""); + mPathToCurrentImageInForm = ""; + refreshPickImageButton(); +} + +void ConversationWidget::refreshPickImageButton() +{ + ui->addImageButton->setFlat(hasUserSelectedImageToSend()); +} + +void ConversationWidget::on_addImageButton_clicked() +{ + setMessageFormImage(); +} + +bool ConversationWidget::hasUserSelectedImageToSend() +{ + return !mPathToCurrentImageInForm.isEmpty(); } diff --git a/widgets/conversationwidget.h b/widgets/conversationwidget.h index 68477ea..a828384 100644 --- a/widgets/conversationwidget.h +++ b/widgets/conversationwidget.h @@ -32,13 +32,17 @@ public: public slots: + /** + * Ask the user to choose an image to send with the form + */ + void setMessageFormImage(); + /** * Send the message entered by the user in the form */ void sendMessage(); - private slots: /** @@ -67,8 +71,18 @@ private slots: void on_messageContentInput_returnPressed(); + void on_addImageButton_clicked(); + private: + /** + * Check out whether the user has selected an image to include + * to the next message he will send through the form + * + * @return TRUE if the user has selected an image / FALSE else + */ + bool hasUserSelectedImageToSend(); + /** * Methods to get and set send message form * lock state @@ -76,9 +90,11 @@ private: void setSendMessageFormLocked(bool lock); bool isSendMessageFormLocked(); void resetSendMessageForm(); + void refreshPickImageButton(); //Private fields Ui::ConversationWidget *ui; + QString mPathToCurrentImageInForm; QTimer *mTimer; ConversationHelper *mConversationHelper; Conversation mConversation; diff --git a/widgets/conversationwidget.ui b/widgets/conversationwidget.ui index b576c60..3a2456b 100644 --- a/widgets/conversationwidget.ui +++ b/widgets/conversationwidget.ui @@ -54,6 +54,20 @@ + + + + + + + + :/baseline_insert_photo_black_48dp.png:/baseline_insert_photo_black_48dp.png + + + false + + + @@ -65,6 +79,8 @@ - + + +