From 942d102eb245c1c313ec263ea2ce5c25bdebae33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 13 Dec 2018 09:49:03 -0500
Subject: [PATCH] chatview: add invitation top bar when necessary

Change-Id: I70d3e10746ed8aa68b351e6d9e03516b82fab3b3
Gitlab: #420
Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
---
 callwidget.cpp     |   7 ++
 messagewebview.cpp |  50 +++++++++++--
 messagewebview.h   |   6 +-
 web/chatview.css   | 172 ++++++++++++++++++++++++++++-----------------
 web/chatview.html  |  25 +++++--
 web/chatview.js    |  30 ++++++--
 6 files changed, 207 insertions(+), 83 deletions(-)

diff --git a/callwidget.cpp b/callwidget.cpp
index 0bf4c09..a2d7daa 100644
--- a/callwidget.cpp
+++ b/callwidget.cpp
@@ -29,6 +29,7 @@
 #include <QComboBox>
 #include <QWebEngineScript>
 
+#include <algorithm>
 #include <memory>
 
 #include "qrencode.h"
@@ -764,6 +765,12 @@ CallWidget::showIMOutOfCall(const QModelIndex& nodeIdx)
     auto contactUri = currentConversation->participants.front();
     try {
         auto& contact = accInfo->contactModel->getContact(contactUri);
+        auto bestName = Utils::bestNameForConversation(*currentConversation, *convModel);
+        ui->messageView->setInvitation(
+            (contact.profileInfo.type == lrc::api::profile::Type::PENDING),
+            bestName,
+            accInfo->contactModel->getContactProfileId(contact.profileInfo.uri)
+        );
         if (!contact.profileInfo.avatar.empty()) {
             ui->messageView->setSenderImage(
                 accInfo->contactModel->getContactProfileId(contactUri),
diff --git a/messagewebview.cpp b/messagewebview.cpp
index 02a37b6..a662e2d 100644
--- a/messagewebview.cpp
+++ b/messagewebview.cpp
@@ -2,7 +2,7 @@
  * Copyright (C) 2017-2018 by Savoir-faire Linux                           *
  * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>          *
  * Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>            *
- * Author: S�bastien Blin <sebastien.blin@savoirfairelinux.com>            *
+ * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>            *
  * Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>              *
  *                                                                         *
  * This program is free software; you can redistribute it and/or modify    *
@@ -213,10 +213,10 @@ MessageWebView::setSenderImage(const std::string& sender,
 }
 
 void
-MessageWebView::setInvitation(bool show, const std::string& contactUri)
+MessageWebView::setInvitation(bool show, const std::string& contactUri, const std::string& contactId)
 {
-    QString s = QString::fromLatin1(show ? "showInvitation(\"%1\")" : "showInvitation()")
-        .arg(QString(contactUri.c_str()));
+    QString s = QString::fromLatin1(show ? "showInvitation(\"%1\", \"%2\")" : "showInvitation()")
+        .arg(QString(contactUri.c_str())).arg(QString(contactId.c_str()));
     page()->runJavaScript(s, QWebEngineScript::MainWorld);
 }
 
@@ -228,6 +228,13 @@ MessageWebView::hideMessages()
 }
 
 // JS bridging incoming
+Q_INVOKABLE int
+PrivateBridging::log(const QString& arg)
+{
+    qDebug() << "JS log: " << arg;
+    return 0;
+}
+
 Q_INVOKABLE int
 PrivateBridging::deleteInteraction(const QString& arg)
 {
@@ -342,8 +349,37 @@ PrivateBridging::sendFile()
 }
 
 Q_INVOKABLE int
-PrivateBridging::log(const QString& arg)
+PrivateBridging::acceptInvitation()
 {
-    qDebug() << "JS log: " << arg;
+    try {
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->makePermanent(convUid);
+    } catch (...) {
+        qDebug() << "JS bridging - exception during acceptInvitation";
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::refuseInvitation()
+{
+    try {
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->removeConversation(convUid, false);
+    } catch (...) {
+        qDebug() << "JS bridging - exception during refuseInvitation";
+    }
+    return 0;
+}
+
+Q_INVOKABLE int
+PrivateBridging::blockConversation()
+{
+    try {
+        auto convUid = LRCInstance::getSelectedConvUid();
+        LRCInstance::getCurrentConversationModel()->removeConversation(convUid, true);
+    } catch (...) {
+        qDebug() << "JS bridging - exception during blockConversation";
+    }
     return 0;
-}
\ No newline at end of file
+}
diff --git a/messagewebview.h b/messagewebview.h
index 72b15e7..5c65ac6 100644
--- a/messagewebview.h
+++ b/messagewebview.h
@@ -39,7 +39,9 @@ public:
     Q_INVOKABLE int sendMessage(const QString & arg);
     Q_INVOKABLE int sendFile();
     Q_INVOKABLE int log(const QString& arg);
-
+    Q_INVOKABLE int acceptInvitation();
+    Q_INVOKABLE int refuseInvitation();
+    Q_INVOKABLE int blockConversation();
 };
 
 class MessageWebView : public QWebEngineView
@@ -68,7 +70,7 @@ public:
                       lrc::api::interaction::Info> interactions);
     void setSenderImage(const std::string& sender,
                         const std::string& senderImage);
-    void setInvitation(bool show, const std::string& contactUri);
+    void setInvitation(bool show, const std::string& contactUri, const std::string& contactId);
     void hideMessages();
 
 private slots:
diff --git a/web/chatview.css b/web/chatview.css
index 2df1bf0..d341345 100644
--- a/web/chatview.css
+++ b/web/chatview.css
@@ -3,6 +3,10 @@
 :root {
     /* color definitions */
     --jami-light-blue: rgba(59, 193, 211, 0.3);
+    --jami-green: #219d55;
+    --jami-green-hover: #1f8b4c;
+    --jami-red: #dc2719;
+    --jami-red-hover: #b02e2c;
     /* main properties */
     /* --bg-color: #f2f2f2; same as macOS client */
     --bg-color: #ffffff; /* same as macOS client */
@@ -17,11 +21,12 @@
     --action-icon-color: #00;
     --deactivated-icon-color: #bebebe;
     --action-icon-hover-color: #ededed;
-    --action-critical-icon-hover-color: rgba(211, 77, 59, 0.3); /* complementary color of ring light blue */
+    --action-critical-icon-hover-color: rgba(211, 77, 59, 0.3); /* complementary color of jami light blue */
     --action-critical-icon-press-color: rgba(211, 77, 59, 0.5);
     --action-critical-icon-color: #4E1300;
     --non-action-icon-color: #212121;
     --action-icon-press-color: rgba(212, 212, 212, 1.0);
+    --invite-hover-color: white;
     /* hairline properties */
     --hairline-color: #f0f0f0;
     --hairline-thickness: 2px;
@@ -189,66 +194,60 @@ body {
 /** Invitation bar */
 
 #invitation {
-    visibility: hidden;
-    background: var(--jami-light-blue);
-    position: absolute;
-    width: 100%;
-    /* hairline */
-    border-bottom: var(--hairline-thickness) solid var(--hairline-color);
+  visibility: hidden;
+  background: var(--bg-color);
+  position: absolute;
+  width: 100%;
+  padding-bottom: 10px;
+
+  /* hairline */
+  border-bottom: var(--hairline-thickness) solid var(--hairline-color);
 }
 
-    #invitation #actions {
-        margin: 10px;
-        list-style: none;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        /* enable selection (it is globally disabled in the body) */
-        -webkit-user-select: auto;
-    }
+#invitation #invite_header {
+  margin: 10px;
+  list-style: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 
-    #invitation #text h1 {
-        font-size: 1.5em;
-    }
+  /* enable selection (it is globally disabled in the body) */
+  -webkit-user-select: auto;
+}
 
-    #invitation #text {
-        text-align: center;
-        width: 90%;
-        margin: auto;
-        margin-top: 10px;
-        font-size: 0.8em;
-        /* enable selection (it is globally disabled in the body) */
-        -webkit-user-select: auto;
-    }
+#invitation .sender_image {
+  width: 50px;
+  height: 50px;
+}
 
-.invitation-button,
-.flat-button {
-    margin: 5px;
-    border: 0;
-    border-radius: 5px;
-    transition: all 0.3s ease;
-    color: #f9f9f9;
-    padding: 10px 20px 10px 20px;
-    vertical-align: middle;
-    cursor: pointer;
+#invitation #actions {
+  margin-right: auto;
+  margin-left: auto;
+  list-style: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+
+  /* enable selection (it is globally disabled in the body) */
+  -webkit-user-select: auto;
 }
 
-.button-green {
-    background: #27ae60;
+#invitation #actions div {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 5px;
+    margin-left: 5px;
 }
 
-    .button-green:hover {
-        background: #1f8b4c;
-    }
+#invitation #text {
+  text-align: center;
+  font-size: 0.8em;
 
-.button-red {
-    background: #dc3a37;
+  /* enable selection (it is globally disabled in the body) */
+  -webkit-user-select: auto;
 }
 
-    .button-red:hover {
-        background: #b02e2c;
-    }
-
 /** Messaging bar */
 
 #sendMessage {
@@ -423,6 +422,8 @@ a:hover {
 .sender_image {
     border-radius: 50%;
     margin: 8px 10px 0px 10px;
+    width: 35px;
+    height: 35px;
 }
 
 .message_out .message_wrapper {
@@ -613,6 +614,13 @@ input[type=checkbox] {
 /* Buttons */
 
 .flat-button {
+    border: 0;
+    border-radius: 5px;
+    transition: all 0.3s ease;
+    color: #f9f9f9;
+    padding: 10px 20px 10px 20px;
+    vertical-align: middle;
+    cursor: pointer;
     flex: 1;
     padding: 0;
 }
@@ -693,35 +701,69 @@ input[type=checkbox] {
     flex-wrap: wrap;
 }
 
+.invite-btn-red {
+    transition: background-color 0.5s ease;
+}
+
+.invite-btn-red:hover {
+    background: var(--jami-red);
+}
+
+.invite-btn-red svg {
+    fill: var(--jami-red);
+    transition: fill 0.5s ease;
+}
+
+.invite-btn-red:hover svg {
+    fill: var(--invite-hover-color);
+}
+
+.invite-btn-green {
+    transition: background-color 0.5s ease;
+}
+
+.invite-btn-green:hover {
+    background: var(--jami-green);
+}
+
+.invite-btn-green svg {
+    fill: var(--jami-green);
+    transition: fill 0.5s ease;
+}
+
+.invite-btn-green:hover svg {
+    fill: var(--invite-hover-color);
+}
+
 .accept, .refuse {
     border-radius: 50%;
     cursor: pointer;
 }
 
-    .accept svg,
-    .refuse svg {
-        padding: 8px;
-        width: 24px;
-        height: 24px;
-    }
+.accept svg,
+.refuse svg {
+    padding: 8px;
+    width: 24px;
+    height: 24px;
+}
 
 .accept {
-    fill: #219d55;
+    fill: var(--jami-green);
 }
 
-    .accept:hover {
-        fill: white;
-        background: #219d55;
-    }
+.accept:hover {
+    fill: var(--invite-hover-color);
+    background: var(--jami-green-hover);
+}
 
 .refuse {
-    fill: #dc2719;
+    fill: var(--jami-red);
 }
 
-    .refuse:hover {
-        fill: white;
-        background: #dc2719;
-    }
+.refuse:hover {
+    fill: var(--invite-hover-color);
+    background: var(--jami-red-hover);
+}
 
 .message_type_data_transfer .text {
     text-align: left;
diff --git a/web/chatview.html b/web/chatview.html
index 43fbb18..209ca42 100644
--- a/web/chatview.html
+++ b/web/chatview.html
@@ -12,12 +12,29 @@
 <body>
     <div class="navbar-wrapper">
         <div id="invitation">
-            <div id="text">
+            <div id="invite_header">
+                <span id="invite_image"></span>
+                <div id="text"></div>
             </div>
             <div id="actions">
-                <div id="accept-btn" class="invitation-button button-green" onclick="acceptInvitation()">Accept</div>
-                <div id="refuse-btn" class="invitation-button button-red" onclick="refuseInvitation()">Refuse</div>
-                <div id="block-btn" class="invitation-button button-red" onclick="blockConversation()">Block</div>
+                <div id="accept-btn" class="nav-button action-button invite-btn-green" onclick="acceptInvitation()" title="Accept">
+                    <svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+                        <path d="M0 0h24v24H0z" fill="none" />
+                        <path d="M15 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm-9-2V7H4v3H1v2h3v3h2v-3h3v-2H6zm9 4c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
+                    </svg>
+                </div>
+                <div id="refuse-btn" class="nav-button action-button invite-btn-red" onclick="refuseInvitation()" title="Refuse">
+                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+                        <path fill="none" d="M0 0h24v24H0V0z" />
+                        <path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
+                    </svg>
+                </div>
+                <div id="block-btn" class="nav-button action-button invite-btn-red" onclick="blockConversation()" title="Block">
+                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+                        <path d="M0 0h24v24H0z" fill="none" />
+                        <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM4 12c0-4.42 3.58-8 8-8 1.85 0 3.55.63 4.9 1.69L5.69 16.9C4.63 15.55 4 13.85 4 12zm8 8c-1.85 0-3.55-.63-4.9-1.69L18.31 7.1C19.37 8.45 20 10.15 20 12c0 4.42-3.58 8-8 8z" />
+                    </svg>
+                </div>
             </div>
         </div>
     </div>
diff --git a/web/chatview.js b/web/chatview.js
index 54994fb..f267101 100644
--- a/web/chatview.js
+++ b/web/chatview.js
@@ -28,6 +28,7 @@ var historyBuffer = []
 const messageBar        = document.getElementById("sendMessage")
 const messageBarInput   = document.getElementById("message")
 const invitation        = document.getElementById("invitation")
+const inviteImage       = document.getElementById("invite_image")
 const invitationText    = document.getElementById("text")
 var   messages          = document.getElementById("messages")
 
@@ -125,19 +126,25 @@ function back_to_bottom() {
  * Otherwise, invitation div is updated.
  *
  * @param contactAlias
+ * @param contactId
  */
 /* exported showInvitation */
-function showInvitation(contactAlias) {
+function showInvitation(contactAlias, contactId) {
     if (!contactAlias) {
         if (hasInvitation) {
             hasInvitation = false
             invitation.style.visibility = ""
         }
     } else {
+        if (!inviteImage.classList.contains('sender_image')) {
+            inviteImage.classList.add('sender_image');
+        }
+        if (!inviteImage.classList.contains(`sender_image_${contactId}`)) {
+            inviteImage.classList.add(`sender_image_${contactId}`);
+        }
         hasInvitation = true
-        invitationText.innerHTML = "<h1>" + contactAlias + " sends you an invitation</h1>"
-      + "Do you want to add them to the conversations list?<br>"
-      + "Note: you can automatically accept this invitation by sending a message."
+        invitationText.innerHTML = "<b>" + contactAlias + " is not in your contacts</b><br/>"
+            + "Note: you can automatically accept this invitation by sending a message."
         invitation.style.visibility = "visible"
     }
 }
@@ -255,6 +262,19 @@ function sendMessage()
     }
 }
 
+/* exported acceptInvitation */
+function acceptInvitation() {
+    window.jsbridge.acceptInvitation()
+}
+/* exported refuseInvitation */
+function refuseInvitation() {
+    window.jsbridge.refuseInvitation()
+}
+/* exported blockConversation */
+function blockConversation() {
+    window.jsbridge.blockConversation()
+}
+
 /* exported sendFile */
 function sendFile()
 {
@@ -1499,6 +1519,6 @@ function setSenderImage(set_sender_image_object)
 
     style.type = "text/css"
     style.id = sender_image_id
-    style.innerHTML = "." + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");height: 32px;width: 32px;}"
+    style.innerHTML = "." + sender_image_id + " {content: url(data:image/png;base64," + sender_image + ");}"
     document.head.appendChild(style)
 }
-- 
GitLab