diff --git a/callwidget.cpp b/callwidget.cpp index 0bf4c095caf39bee3ba3d075f24fa74b9629529a..a2d7daace8e1a2e779deb87d890d1ffe3190b8d2 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 02a37b6b431b208aa4c4e654fa72eb3e141158a1..a662e2d587d383a470a0c6cc1791c28a70095dcc 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 72b15e783bc03718b208177d470ba482176a9079..5c65ac667701b3c8f5285b0fe5f479cc4fd8bffa 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 2df1bf0da7ca5d777590fb09db66439c8f20b409..d341345ebaf52702569a5d3b1f54c3cd895ea6f2 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 43fbb18f3892d871fb1da5f7274ffc6b44ace583..209ca42cd92cf8d7df9eebbc7f96e8517cc9beae 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 54994fbb5a6d2e5cd5a794a560fc607529c3458f..f267101c33cba933a6a1f53f2fa6f15bd8974c46 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) }