chatview: Add data transfer support

Add the ability to send and receives files via the chatview.

Change-Id: I529b3e1c75030c3bc2c5e535e9e9584dde9bb4cb
Reviewed-by: Philippe Gorley <philippe.gorley@savoirfairelinux.com>
diff --git a/src/chatview.cpp b/src/chatview.cpp
index 23d7f23..3d7b006 100644
--- a/src/chatview.cpp
+++ b/src/chatview.cpp
@@ -25,6 +25,9 @@
 // std
 #include <algorithm>
 
+// GTK
+#include <glib/gi18n.h>
+
 // LRC
 #include <api/contactmodel.h>
 #include <api/conversationmodel.h>
@@ -160,6 +163,34 @@
     priv->accountContainer_->info.conversationModel->makePermanent(priv->conversation_->uid);
 }
 
+static gchar*
+file_to_manipulate(GtkWindow* top_window, bool send)
+{
+    GtkWidget* dialog;
+    GtkFileChooserAction action = send? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE;
+    gint res;
+    gchar* filename = nullptr;
+
+    dialog = gtk_file_chooser_dialog_new(send? _("Send File") : _("Save File"),
+                                         top_window,
+                                         action,
+                                         _("_Cancel"),
+                                         GTK_RESPONSE_CANCEL,
+                                         send? _("_Open"): _("_Save"),
+                                         GTK_RESPONSE_ACCEPT,
+                                         nullptr);
+
+    res = gtk_dialog_run (GTK_DIALOG(dialog));
+
+    if (res == GTK_RESPONSE_ACCEPT) {
+        auto chooser = GTK_FILE_CHOOSER(dialog);
+        filename = gtk_file_chooser_get_filename(chooser);
+    }
+    gtk_widget_destroy (dialog);
+
+    return filename;
+}
+
 static void
 webkit_chat_container_script_dialog(G_GNUC_UNUSED GtkWidget* webview, gchar *interaction, ChatView* self)
 {
@@ -176,6 +207,42 @@
         // Get text body
         auto toSend = order.substr(std::string("SEND:").size());
         priv->accountContainer_->info.conversationModel->sendMessage(priv->conversation_->uid, toSend);
+    } else if (order.find("SEND_FILE") == 0) {
+        if (auto model = priv->accountContainer_->info.conversationModel.get()) {
+            if (auto filename = file_to_manipulate(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))), true))
+                model->sendFile(priv->conversation_->uid, filename, g_path_get_basename(filename));
+        }
+    } else if (order.find("ACCEPT_FILE:") == 0) {
+        if (auto model = priv->accountContainer_->info.conversationModel.get()) {
+            try {
+                auto interactionId = std::stoull(order.substr(std::string("ACCEPT_FILE:").size()));
+                if (auto filename = file_to_manipulate(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))), false))
+                    model->acceptTransfer(priv->conversation_->uid, interactionId, filename);
+                else
+                    model->cancelTransfer(priv->conversation_->uid, interactionId);
+            } catch (...) {
+                // ignore
+            }
+        }
+    } else if (order.find("REFUSE_FILE:") == 0) {
+        if (auto model = priv->accountContainer_->info.conversationModel.get()) {
+            try {
+                auto interactionId = std::stoull(order.substr(std::string("REFUSE_FILE:").size()));
+                model->cancelTransfer(priv->conversation_->uid, interactionId);
+            } catch (...) {
+                // ignore
+            }
+        }
+    } else if (order.find("OPEN_FILE:") == 0) {
+        // Get text body
+        auto filename {"file://" + order.substr(std::string("OPEN_FILE:").size())};
+        filename.erase(std::find_if(filename.rbegin(), filename.rend(),
+            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), filename.end());
+        GError* error = nullptr;
+        if (!gtk_show_uri(nullptr, filename.c_str(), GDK_CURRENT_TIME, &error)) {
+            g_debug("Could not open file: %s", error->message);
+            g_error_free(error);
+        }
     }
 }
 
@@ -242,6 +309,7 @@
 
     webkit_chat_container_print_new_interaction(
         WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
+        *priv->accountContainer_->info.conversationModel,
         interactionId,
         interaction
     );
@@ -253,6 +321,7 @@
     ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
     webkit_chat_container_update_interaction(
         WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
+        *priv->accountContainer_->info.conversationModel,
         interactionId,
         interaction
     );
@@ -299,6 +368,7 @@
     if (!priv->conversation_) return;
     webkit_chat_container_print_history(
         WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
+        *priv->accountContainer_->info.conversationModel,
         priv->conversation_->interactions
     );
 
diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp
index c7be93a..2c58072 100644
--- a/src/ringmainwindow.cpp
+++ b/src/ringmainwindow.cpp
@@ -1001,7 +1001,7 @@
 
     gtk_combo_box_set_model(
         GTK_COMBO_BOX(widgets->combobox_account_selector),
-        GTK_TREE_MODEL (store)
+        GTK_TREE_MODEL(store)
     );
     gtk_combo_box_set_active(GTK_COMBO_BOX(widgets->combobox_account_selector), selection_row);
     gtk_widget_set_visible(widgets->combobox_account_selector, show && enabled_accounts > 1);
diff --git a/src/webkitchatcontainer.cpp b/src/webkitchatcontainer.cpp
index bd58fc2..f8700ef 100644
--- a/src/webkitchatcontainer.cpp
+++ b/src/webkitchatcontainer.cpp
@@ -31,6 +31,7 @@
 
 // LRC
 #include <globalinstances.h>
+#include <api/conversationmodel.h>
 
 // Ring Client
 #include "native/pixbufmanipulator.h"
@@ -142,7 +143,9 @@
 }
 
 QJsonObject
-build_interaction_json(const uint64_t msgId, const lrc::api::interaction::Info& interaction)
+build_interaction_json(lrc::api::ConversationModel& conversation_model,
+                       const uint64_t msgId,
+                       const lrc::api::interaction::Info& interaction)
 {
     auto sender = QString(interaction.authorUri.c_str());
     auto timestamp = QString::number(interaction.timestamp);
@@ -155,6 +158,7 @@
     interaction_object.insert("sender_contact_method", QJsonValue(sender));
     interaction_object.insert("timestamp", QJsonValue(timestamp));
     interaction_object.insert("direction", QJsonValue(direction));
+
     switch (interaction.type)
     {
     case lrc::api::interaction::Type::TEXT:
@@ -166,11 +170,23 @@
     case lrc::api::interaction::Type::CONTACT:
         interaction_object.insert("type", QJsonValue("contact"));
         break;
+    case lrc::api::interaction::Type::OUTGOING_DATA_TRANSFER:
+    case lrc::api::interaction::Type::INCOMING_DATA_TRANSFER: {
+        interaction_object.insert("type", QJsonValue("data_transfer"));
+        lrc::api::datatransfer::Info info = {};
+        conversation_model.getTransferInfo(msgId, info);
+        if (info.status != lrc::api::datatransfer::Status::INVALID) {
+            interaction_object.insert("totalSize", QJsonValue(qint64(info.totalSize)));
+            interaction_object.insert("progress", QJsonValue(qint64(info.progress)));
+        }
+        break;
+    }
     case lrc::api::interaction::Type::INVALID:
     default:
         interaction_object.insert("type", QJsonValue(""));
         break;
     }
+
     switch (interaction.status)
     {
     case lrc::api::interaction::Status::READ:
@@ -180,11 +196,33 @@
         interaction_object.insert("delivery_status", QJsonValue("sent"));
         break;
     case lrc::api::interaction::Status::FAILED:
+    case lrc::api::interaction::Status::TRANSFER_ERROR:
         interaction_object.insert("delivery_status", QJsonValue("failure"));
         break;
+    case lrc::api::interaction::Status::TRANSFER_UNJOINABLE_PEER:
+        interaction_object.insert("delivery_status", QJsonValue("unjoinable peer"));
+        break;
     case lrc::api::interaction::Status::SENDING:
         interaction_object.insert("delivery_status", QJsonValue("sending"));
         break;
+    case lrc::api::interaction::Status::TRANSFER_CREATED:
+        interaction_object.insert("delivery_status", QJsonValue("connecting"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_ACCEPTED:
+        interaction_object.insert("delivery_status", QJsonValue("accepted"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_CANCELED:
+        interaction_object.insert("delivery_status", QJsonValue("canceled"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_ONGOING:
+        interaction_object.insert("delivery_status", QJsonValue("ongoing"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_AWAITING:
+        interaction_object.insert("delivery_status", QJsonValue("awaiting"));
+        break;
+    case lrc::api::interaction::Status::TRANSFER_FINISHED:
+        interaction_object.insert("delivery_status", QJsonValue("finished"));
+        break;
     case lrc::api::interaction::Status::INVALID:
     case lrc::api::interaction::Status::UNKNOWN:
     case lrc::api::interaction::Status::UNREAD:
@@ -196,17 +234,20 @@
 }
 
 QString
-interaction_to_json_interaction_object(const uint64_t msgId, const lrc::api::interaction::Info& interaction)
+interaction_to_json_interaction_object(lrc::api::ConversationModel& conversation_model,
+                                       const uint64_t msgId,
+                                       const lrc::api::interaction::Info& interaction)
 {
-    auto interaction_object = build_interaction_json(msgId, interaction);
+    auto interaction_object = build_interaction_json(conversation_model, msgId, interaction);
     return QString(QJsonDocument(interaction_object).toJson(QJsonDocument::Compact));
 }
 
 QString
-interactions_to_json_array_object(const std::map<uint64_t, lrc::api::interaction::Info> interactions) {
+interactions_to_json_array_object(lrc::api::ConversationModel& conversation_model,
+                                  const std::map<uint64_t, lrc::api::interaction::Info> interactions) {
     QJsonArray array;
     for (const auto& interaction: interactions)
-        array.append(build_interaction_json(interaction.first, interaction.second));
+        array.append(build_interaction_json(conversation_model, interaction.first, interaction.second));
     return QString(QJsonDocument(array).toJson(QJsonDocument::Compact));
 }
 
@@ -551,12 +592,13 @@
 
 void
 webkit_chat_container_update_interaction(WebKitChatContainer *view,
+                                         lrc::api::ConversationModel& conversation_model,
                                          uint64_t msgId,
                                          const lrc::api::interaction::Info& interaction)
 {
     WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
 
-    auto interaction_object = interaction_to_json_interaction_object(msgId, interaction).toUtf8();
+    auto interaction_object = interaction_to_json_interaction_object(conversation_model, msgId, interaction).toUtf8();
     gchar* function_call = g_strdup_printf("ring.chatview.updateMessage(%s);", interaction_object.constData());
     webkit_web_view_run_javascript(
         WEBKIT_WEB_VIEW(priv->webview_chat),
@@ -570,12 +612,13 @@
 
 void
 webkit_chat_container_print_new_interaction(WebKitChatContainer *view,
+                                            lrc::api::ConversationModel& conversation_model,
                                             uint64_t msgId,
                                             const lrc::api::interaction::Info& interaction)
 {
     WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
 
-    auto interaction_object = interaction_to_json_interaction_object(msgId, interaction).toUtf8();
+    auto interaction_object = interaction_to_json_interaction_object(conversation_model, msgId, interaction).toUtf8();
     gchar* function_call = g_strdup_printf("ring.chatview.addMessage(%s);", interaction_object.constData());
     webkit_web_view_run_javascript(
         WEBKIT_WEB_VIEW(priv->webview_chat),
@@ -588,11 +631,13 @@
 }
 
 void
-webkit_chat_container_print_history(WebKitChatContainer *view, const std::map<uint64_t, lrc::api::interaction::Info> interactions)
+webkit_chat_container_print_history(WebKitChatContainer *view,
+                                    lrc::api::ConversationModel& conversation_model,
+                                    const std::map<uint64_t, lrc::api::interaction::Info> interactions)
 {
     WebKitChatContainerPrivate *priv = WEBKIT_CHAT_CONTAINER_GET_PRIVATE(view);
 
-    auto interactions_str = interactions_to_json_array_object(interactions).toUtf8();
+    auto interactions_str = interactions_to_json_array_object(conversation_model, interactions).toUtf8();
     gchar* function_call = g_strdup_printf("ring.chatview.printHistory(%s)", interactions_str.constData());
     webkit_web_view_run_javascript(
         WEBKIT_WEB_VIEW(priv->webview_chat),
diff --git a/src/webkitchatcontainer.h b/src/webkitchatcontainer.h
index 5afaa47..6efefde 100644
--- a/src/webkitchatcontainer.h
+++ b/src/webkitchatcontainer.h
@@ -27,6 +27,10 @@
 // LRC
 #include <api/interaction.h>
 
+namespace lrc { namespace api {
+class ConversationModel;
+}};
+
 G_BEGIN_DECLS
 
 #define WEBKIT_CHAT_CONTAINER_TYPE            (webkit_chat_container_get_type ())
@@ -42,9 +46,9 @@
 GtkWidget* webkit_chat_container_new                  (void);
 void       webkit_chat_container_clear                (WebKitChatContainer *view);
 void       webkit_chat_container_clear_sender_images  (WebKitChatContainer *view);
-void       webkit_chat_container_print_new_interaction(WebKitChatContainer *view, uint64_t msgId, const lrc::api::interaction::Info& interaction);
-void       webkit_chat_container_print_history        (WebKitChatContainer *view, const std::map<uint64_t, lrc::api::interaction::Info> interactions);
-void       webkit_chat_container_update_interaction   (WebKitChatContainer *view, uint64_t msgId, const lrc::api::interaction::Info& interaction);
+void       webkit_chat_container_print_new_interaction(WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, uint64_t msgId, const lrc::api::interaction::Info& interaction);
+void       webkit_chat_container_update_interaction   (WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, uint64_t msgId, const lrc::api::interaction::Info& interaction);
+void       webkit_chat_container_print_history        (WebKitChatContainer *view, lrc::api::ConversationModel& conversation_model, const std::map<uint64_t, lrc::api::interaction::Info> interactions);
 void       webkit_chat_container_set_sender_image     (WebKitChatContainer *view, const std::string& sender, const std::string& senderImage);
 gboolean   webkit_chat_container_is_ready             (WebKitChatContainer *view);
 void       webkit_chat_container_set_display_links    (WebKitChatContainer *view, bool display);
diff --git a/web/chatview.css b/web/chatview.css
index ba4af18..07be2e5 100644
--- a/web/chatview.css
+++ b/web/chatview.css
@@ -32,7 +32,6 @@
   color: white;
   padding: 10px 20px 10px 20px;
   vertical-align: middle;
-
 }
 
 .button-green {
@@ -169,7 +168,7 @@
     color: #d3d3d3;
 }
 
-#sendBtn {
+.msg-button {
   border-radius: 50%;
   border: 0;
   width: 40px;
@@ -178,13 +177,13 @@
   transition: all 0.3s ease;
 }
 
-#sendBtn.hover,
-#sendBtn:hover {
+.msg-button.hover,
+.msg-button:hover {
   background: #bae5f0;
 }
 
-#sendBtn.hover svg,
-#sendBtn:hover svg {
+.msg-button.hover svg,
+.msg-button:hover svg {
   fill: black;
 }
 
@@ -502,26 +501,34 @@
   width: 100%;
 }
 
+.message_type_data_transfer .message_timestamp,
+.message_type_data_transfer .message_delivery_status,
+.message_type_data_transfer .sent-checkmark,
 .message_type_call .sent-checkmark,
 .message_type_call .message_sender,
 .message_type_call .message_delivery_status,
 .message_type_call .message_timestamp,
 .message_type_call .message_sender_image,
+.message_type_call .message_progress_bar,
 .message_type_contact .sent-checkmark,
 .message_type_contact .message_sender,
 .message_type_contact .message_delivery_status,
 .message_type_contact .message_timestamp,
-.message_type_contact .message_sender_image {
+.message_type_contact .message_sender_image,
+.message_type_contact .message_progress_bar,
+.message_type_text .message_progress_bar {
   visibility: hidden;
   display: none;
 }
 
 .message_type_contact .message_wrapper:before,
+.message_type_data_transfer .message_wrapper:before,
 .message_type_call .message_wrapper:before {
     display: none;
 }
 
 .message_type_call .message_wrapper:after,
+.message_type_data_transfer .message_wrapper:after,
 .message_type_contact .message_wrapper:after {
     display: none;
 }
@@ -561,3 +568,63 @@
   color: red;
   font-size: 1.25em;
 }
+
+.message_type_data_transfer .message_wrapper
+{
+  padding: 0;
+  width: 30%;
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.message_type_data_transfer #left_buttons
+{
+  width: 25%;
+  display: flex;
+  padding-left: 5px;
+  overflow: hidden;
+}
+
+.message_type_data_transfer #text
+{
+  width: 70%;
+  text-align: center;
+  padding-top: 14px;
+  margin-bottom: 12px;
+}
+
+.message_type_data_transfer #filename
+{
+  font-weight: bold;
+  overflow: hidden;
+}
+
+.message_type_data_transfer #informations
+{
+  color: #555;
+  font-size: 0.8em;
+}
+
+.message_progress_bar {
+  margin-top: 12px;
+  width: 100%;
+  height: 12px;
+  position: relative;
+  overflow: hidden;
+  background-color: #eee;
+  border-radius: 2px;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25) inset;
+}
+
+.message_progress_bar > span {
+  display: inline;
+  height: 100%;
+  background-color: #01a2b8;
+  position: absolute;
+  overflow: hidden;
+}
+
+.message_type_data_transfer .flat-button
+{
+  padding: 0;
+}
diff --git a/web/chatview.html b/web/chatview.html
index 435ac79..721457e 100644
--- a/web/chatview.html
+++ b/web/chatview.html
@@ -20,11 +20,17 @@
 
       <div id="sendMessage">
         <textarea id="message" autofocus placeholder="Message" onkeyup="grow_text_area()" rows="1" disabled="false"></textarea>
-        <div id="sendBtn" onclick="ring.chatview.sendMessage()" title="Send">
+        <div id="sendBtn" class="msg-button" onclick="ring.chatview.sendMessage()" title="Send">
           <svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
               <path xmlns="http://www.w3.org/2000/svg" d="M12,11.874v4.357l7-6.69l-7-6.572v3.983c-8.775,0-11,9.732-11,9.732C3.484,12.296,7.237,11.874,12,11.874z"/>
           </svg>
         </div>
+        <div id="send-file-btn" class="msg-button" onclick="ring.chatview.sendFile()" title="Send File">
+          <svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
+              <path d="M0 0h24v24H0z" fill="none"/>
+              <path d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z"/>
+          </svg>
+        </div>
       </div>
       </form>
     </div>
@@ -283,22 +289,22 @@
         }
     }
 
-  /**
-   * Returns HTML message from the message text.
-   * Cleaned and linkified.
-   */
-  function getMessageHtml(message_text)
-  {
-    const escaped_message = escapeHtml(message_text);
-    var linkified_message = linkifyHtml(escaped_message, {});
+    /**
+     * Returns HTML message from the message text.
+     * Cleaned and linkified.
+     */
+    function getMessageHtml(message_text)
+    {
+      const escaped_message = escapeHtml(message_text);
+      var linkified_message = linkifyHtml(escaped_message, {});
 
-    const textPart = document.createElement('pre');
-    var linkified_message = linkified_message.replace("📞", "<span id=\"green\">📞</span>")
-    var linkified_message = linkified_message.replace("🕽", "<span id=\"red\">🕽</span>")
-    textPart.innerHTML = linkified_message;
+      const textPart = document.createElement('pre');
+      var linkified_message = linkified_message.replace("📞", "<span id=\"green\">📞</span>")
+      var linkified_message = linkified_message.replace("🕽", "<span id=\"red\">🕽</span>")
+      textPart.innerHTML = linkified_message;
 
-    return textPart.outerHTML;
-  }
+      return textPart.outerHTML;
+    }
 
     /**
      * Returns the message status, formatted for display
@@ -309,19 +315,17 @@
 
         switch(message_delivery_status)
         {
-            case "unknown":
-                formatted_delivery_status = "";
-                break;
             case "sending":
-                formatted_delivery_status = "Sending<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><circle class='status_circle anim-first' cx='4' cy='12' r='1'/><circle class='status_circle anim-second' cx='8' cy='12' r='1'/><circle class='status_circle anim-third' cx='12' cy='12' r='1'/></svg>"
-                break;
-            case "read":
-                formatted_delivery_status = "";
+            case "ongoing":
+                formatted_delivery_status = "Sending<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><circle class='status_circle anim-first' cx='4' cy='12' r='1'/><circle class='status_circle anim-second' cx='8' cy='12' r='1'/><circle class='status_circle anim-third' cx='12' cy='12' r='1'/></svg>";
                 break;
             case "failure":
-                formatted_delivery_status = "Failure <svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-x x-first' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M4,4 L12,12'/><path class='status-x x-second' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M12,4 L4,12'/></svg>"
+                formatted_delivery_status = "Failure <svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-x x-first' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M4,4 L12,12'/><path class='status-x x-second' stroke='#AA0000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M12,4 L4,12'/></svg>";
                 break;
             case "sent":
+            case "finished":
+            case "unknown":
+            case "read":
                 formatted_delivery_status = "";
                 break;
             default:
@@ -403,6 +407,115 @@
         }
     }
 
+    /**
+     * Convert a value in filesize
+     */
+     function humanFileSize(bytes) {
+        var thresh = 1024;
+        if(Math.abs(bytes) < thresh) {
+            return bytes + ' B';
+        }
+        var units = ['kB','MB','GB','TB','PB','EB','ZB','YB']
+        var u = -1;
+        do {
+            bytes /= thresh;
+            ++u;
+        } while(Math.abs(bytes) >= thresh && u < units.length - 1);
+        return bytes.toFixed(1)+' '+units[u];
+    }
+
+    /**
+     * Change the value of the progress bar
+     */
+    function updateProgressBar(progress_bar, message_object, message_delivery_status) {
+      var delivery_status = message_object["delivery_status"];
+      if ("progress" in message_object && !isErrorStatus(delivery_status) && message_object["progress"] !== 100) {
+        var progress_percent = (100 * message_object["progress"] / message_object["totalSize"]);
+        if (progress_percent !== 100)
+          progress_bar.childNodes[0].setAttribute("style", "width: " + progress_percent + "%");
+        else
+          progress_bar.setAttribute("style", "display: none");
+      } else
+        progress_bar.setAttribute("style", "display: none");
+    }
+
+    /**
+     * Check if a status is an error status
+     */
+    function isErrorStatus(status) {
+      return (status === 'failure'
+             || status === 'canceled'
+             || status === 'unjoinable peer');
+    }
+
+    /**
+     * Update a data transfer interaction
+     */
+     function updateDataTransferInteraction(message_div, message_object) {
+       var acceptSvg = '<svg fill="#ffffff" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%; display:block; margin:auto;"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>',
+           refuseSvg = '<svg fill="#ffffff" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%; display:block; margin:auto;"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
+           fileSvg = '<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%;"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>',
+           warningSvg = '<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%;"><path d="M0 0h24v24H0z" fill="none"/><path d="M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z"/></svg>',
+           incomingSvg = '<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%;"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 5.41L18.59 4 7 15.59V9H5v10h10v-2H8.41z"/></svg>',
+           outgoingSvg = '<svg fill="#000000" height="36" viewBox="0 0 24 24" width="36" xmlns="http://www.w3.org/2000/svg" style="width:50%;"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5z"/></svg>';
+       var message_delivery_status = message_object["delivery_status"];
+       var message_direction = message_object["direction"];
+       var message_id = message_object["id"];
+
+       // Set informations text
+       var informations_div = message_div.querySelector("#informations");
+       var informations_txt = getMessageTimestampText(message_object["timestamp"], true);
+       if (message_object["totalSize"] && message_object["progress"]) {
+         if (message_delivery_status === 'finished') {
+           informations_txt += " - " + humanFileSize(message_object["totalSize"]);
+         } else {
+           informations_txt += " - " + humanFileSize(message_object["progress"])
+                             + " / " + humanFileSize(message_object["totalSize"]);
+         }
+       }
+       informations_txt += " - " + message_delivery_status;
+       informations_div.innerText = informations_txt;
+
+       // Update flat buttons
+       var left_buttons = message_div.querySelector("#left_buttons");
+       left_buttons.innerHTML = '';
+       if (message_delivery_status === 'awaiting') {
+         // add buttons to accept or refuse a call.
+         var accept_button = document.createElement('div');
+         accept_button.innerHTML = acceptSvg;
+         accept_button.setAttribute("id", "accept-btn");
+         accept_button.setAttribute("title", "Accept");
+         accept_button.setAttribute("class", "flat-button button-green");
+         accept_button.onclick = function() {
+           window.prompt('ACCEPT_FILE:' + message_id);
+         }
+         left_buttons.appendChild(accept_button);
+         var refuse_button = document.createElement('div');
+         refuse_button.innerHTML = refuseSvg;
+         refuse_button.setAttribute("id", "refuse-btn");
+         refuse_button.setAttribute("title", "Refuse");
+         refuse_button.setAttribute("class", "flat-button button-red");
+         refuse_button.onclick = function() {
+           window.prompt('REFUSE_FILE:' + message_id);
+         }
+         left_buttons.appendChild(refuse_button);
+       } else {
+         var status_button = document.createElement('div');
+         var statusFile = fileSvg;
+         if (isErrorStatus(message_delivery_status))
+           statusFile = warningSvg;
+         status_button.innerHTML = statusFile;
+         status_button.setAttribute("class", "flat-button");
+         left_buttons.appendChild(status_button);
+         var direction_button = document.createElement('div');
+         var directionFile = message_direction === 'out' ? outgoingSvg: incomingSvg;
+         direction_button.innerHTML = directionFile;
+         direction_button.setAttribute("class", "flat-button");
+         left_buttons.appendChild(direction_button);
+       }
+       updateProgressBar(chatview_message_transfer_progress_bar, message_object);
+     }
+
    /**
     * Adds a message to the buffer, or update it if new_message is
     * TRUE
@@ -428,10 +541,10 @@
             chatview_message_sender_span,
             chatview_message_sender_image,
             chatview_message_div,
-            sentAnimation = "<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-check' stroke='#008000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M2,8 L6,12 L14,4'/></svg>",
-            chatview_sentCheckmark = document.createElement('span');
+            sentAnimation = "<svg overflow='visible' viewBox='0 -2 16 14' height='16px' width='16px'><path class='status-check' stroke='#008000' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' fill='none' d='M2,8 L6,12 L14,4'/></svg>";
 
-            chatview_sentCheckmark.setAttribute("class", "sent-checkmark");
+        var chatview_sentCheckmark = document.createElement('span');
+        chatview_sentCheckmark.setAttribute("class", "sent-checkmark");
 
         chatview_message_div = document.querySelector("#message_" + message_id);
         if (new_message)
@@ -449,32 +562,56 @@
             chatview_message_wrapper = document.createElement('div');
             chatview_message_wrapper.setAttribute("class", "message_wrapper wc");
 
-            chatview_message_text = document.createElement('span');
-            chatview_message_text.setAttribute("class", "message_text");
+            if (message_type === 'data_transfer') {
+              var leftbuttons_div = document.createElement('div');
+              leftbuttons_div.setAttribute("id", "left_buttons");
+              chatview_message_wrapper.appendChild(leftbuttons_div);
+
+              var text_div = document.createElement('div');
+              text_div.setAttribute("id", "text");
+              text_div.addEventListener('click', function (event) {
+                // ask the soft to open the file
+                var filename = document.querySelector("#message_" + message_id + " #filename").innerText;
+                window.prompt('OPEN_FILE:' + filename);
+              });
+              chatview_message_text = document.createElement('div');
+              chatview_message_text.setAttribute("id", "filename");
+              var informations_div = document.createElement('div');
+              informations_div.setAttribute("id", "informations");
+              text_div.appendChild(chatview_message_text);
+              text_div.appendChild(informations_div);
+              chatview_message_wrapper.appendChild(text_div);
+
+              // DataTransfer progress bar
+              chatview_message_transfer_progress_bar = document.createElement('span');
+              chatview_message_transfer_progress_bar.setAttribute("class", "message_progress_bar");
+              chatview_message_transfer_progress_completion = document.createElement('span');
+              chatview_message_transfer_progress_bar.appendChild(chatview_message_transfer_progress_completion);
+              chatview_message_wrapper.appendChild(chatview_message_transfer_progress_bar);
+            } else {
+              chatview_message_text = document.createElement('span');
+              chatview_message_text.setAttribute("class", "message_text");
+              chatview_message_wrapper.appendChild(chatview_message_text);
+              chatview_message_delivery_status = document.createElement('span');
+              chatview_message_delivery_status.setAttribute("class", "message_delivery_status");
+            }
+
 
             chatview_message_sender = document.createElement('span');
             chatview_message_sender.setAttribute("class", "message_sender");
-
-            chatview_message_delivery_status = document.createElement('span');
-            chatview_message_delivery_status.setAttribute("class", "message_delivery_status");
-
-            chatview_message_timestamp = document.createElement('span');
-            chatview_message_timestamp.setAttribute("class", "message_timestamp");
-
-            chatview_message_wrapper.appendChild(chatview_message_text);
             chatview_message_wrapper.appendChild(chatview_message_sender);
-
-            // Sender image
             chatview_message_sender_span = document.createElement('span');
             chatview_message_sender_span.setAttribute("class", "message_sender_image");
-
             chatview_message_sender_image = document.createElement('span');
             chatview_message_sender_image.setAttribute("class", "sender_image sender_image_" + message_sender_contact_method);
+            chatview_message_timestamp = document.createElement('span');
+            chatview_message_timestamp.setAttribute("class", "message_timestamp");
 
             // Append elements to div
             chatview_message_div.appendChild(chatview_message_sender_image);
             chatview_message_div.appendChild(chatview_message_wrapper);
-            chatview_message_div.appendChild(chatview_message_delivery_status);
+            if (message_type !== 'data_transfer')
+              chatview_message_div.appendChild(chatview_message_delivery_status);
 
             // Get timestamp to add
             const formattedTimestamp = getMessageTimestampText(message_timestamp, true);
@@ -487,7 +624,7 @@
             ];
             date_elt.setAttribute("class", timestamp_div_classes.join(" "));
             date_elt.setAttribute("message_timestamp", message_timestamp);
-            // Remove last timestamp if it's the same
+            // Remove last timestamp if it's the same<h6></h6>
             if (messages.querySelectorAll(".timestamp"))
                 cleanPreviousTimestamps(date_elt, messages.children.length);
 
@@ -500,20 +637,17 @@
               messages.insertBefore(chatview_message_div, messages.firstChild);
             }
         } else {
-
-            chatview_message_div = document.querySelector("#message_" + message_id);
-
             if (chatview_message_div) {
+              if (message_type === 'data_transfer') {
+                chatview_message_text = chatview_message_div.querySelector("#filename");
+              } else {
                 chatview_message_text = chatview_message_div.querySelector(".message_text");
-                chatview_message_sender_image = chatview_message_div.querySelector(".message_sender_image");
-                chatview_message_sender = chatview_message_div.querySelector(".message_sender");
                 chatview_message_delivery_status = chatview_message_div.querySelector(".message_delivery_status");
-                chatview_message_timestamp = chatview_message_div.querySelector(".message_timestamp");
-
+              }
+              chatview_message_timestamp = chatview_message_div.querySelector(".message_timestamp");
+              chatview_message_sender = chatview_message_div.querySelector(".message_sender");
             } else {
-
                 console.log('no msg selector.');
-
             }
         }
 
@@ -522,20 +656,25 @@
         if (new_message && displayLinksEnabled)
             displayLinks(chatview_message_text);
         chatview_message_sender.textContent = message_sender + ": ";
-        chatview_message_delivery_status.innerHTML = getMessageDeliveryStatusText(message_delivery_status);
         chatview_message_timestamp.textContent = getMessageTimestampText(message_timestamp);
 
-        if (new_message) {
-            if (message_direction === "out") {
-                chatview_sentCheckmark.innerHTML = sentAnimation;
-                chatview_message_div.querySelector(".message_wrapper").appendChild(chatview_sentCheckmark);
-            }
+        if (message_type === 'data_transfer') {
+          updateDataTransferInteraction(chatview_message_div, message_object);
+        } else {
+          chatview_message_delivery_status.innerHTML = getMessageDeliveryStatusText(message_delivery_status);
+        }
 
-            chatview_message_div.querySelector(".message_wrapper").appendChild(chatview_message_timestamp);
+        // Add timestamp and sent checkmark
+        if (new_message) {
+          if (message_direction === "out") {
+              chatview_sentCheckmark.innerHTML = sentAnimation;
+              chatview_message_div.querySelector(".message_wrapper").appendChild(chatview_sentCheckmark);
+          }
+          chatview_message_div.querySelector(".message_wrapper").appendChild(chatview_message_timestamp);
         }
 
         if (message_direction === "out") {
-            if (message_delivery_status === "sent") {
+            if (message_delivery_status === "sent" || message_delivery_status === "finished") {
                 chatview_message_div.classList.add("message--sent");
             }
         }
@@ -678,6 +817,11 @@
       }
     }
 
+    function sendFile()
+    {
+      window.prompt('SEND_FILE');
+    }
+
     return {
         addMessage: addMessage,
         printHistory: printHistory,
@@ -696,6 +840,8 @@
         showInvitation: showInvitation,
         hideInvitation: hideInvitation,
         disableSendMessage: disableSendMessage,
+        updateTimestamps: updateTimestamps,
+        sendFile: sendFile,
     }
 
 })();