From 44dba711794e78016392f8e0769339f2a189fd63 Mon Sep 17 00:00:00 2001 From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com> Date: Tue, 25 Aug 2020 14:32:34 -0400 Subject: [PATCH] mainview: add sip input panel Add sip input panel to be able to use DTMF functionalities. Gitlab: #18 Change-Id: Iaa53ae2b34d9ce0d5cf19aa82dd41a5607203c62 --- images/icons/ic_keypad.svg | 51 ++++++ images/icons/icon-keypad-24-2x.png | Bin 4183 -> 0 bytes images/icons/icon-keypad-24.png | Bin 879 -> 0 bytes qml.qrc | 1 + ressources.qrc | 3 +- src/calladapter.cpp | 163 ++++++++++-------- src/calladapter.h | 55 +++--- src/commoncomponents/HoverableButton.qml | 14 +- src/constant/JamiTheme.qml | 3 + src/mainview/components/CallOverlay.qml | 6 + .../components/CallOverlayButtonGroup.qml | 2 - .../components/CallViewContextMenu.qml | 17 ++ src/mainview/components/SipInputPanel.qml | 92 ++++++++++ 13 files changed, 305 insertions(+), 102 deletions(-) create mode 100644 images/icons/ic_keypad.svg delete mode 100644 images/icons/icon-keypad-24-2x.png delete mode 100644 images/icons/icon-keypad-24.png create mode 100644 src/mainview/components/SipInputPanel.qml diff --git a/images/icons/ic_keypad.svg b/images/icons/ic_keypad.svg new file mode 100644 index 000000000..c55cdf4fa --- /dev/null +++ b/images/icons/ic_keypad.svg @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"> +<g> + <g> + <g> + <path d="M64,0C28.715,0,0,28.715,0,64s28.715,64,64,64s64-28.715,64-64S99.285,0,64,0z"/> + <path d="M64,192c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S99.285,192,64,192z"/> + <path d="M64,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64s64-28.715,64-64C128,412.715,99.285,384,64,384z"/> + <path d="M256,0c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S291.285,0,256,0z"/> + <path d="M256,192c-35.285,0-64,28.715-64,64s28.715,64,64,64s64-28.715,64-64S291.285,192,256,192z"/> + <path d="M256,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64s64-28.715,64-64C320,412.715,291.285,384,256,384z"/> + <path d="M448,128c35.285,0,64-28.715,64-64S483.285,0,448,0c-35.285,0-64,28.715-64,64S412.715,128,448,128z"/> + <path d="M448,192c-35.285,0-64,28.715-64,64s28.715,64,64,64c35.285,0,64-28.715,64-64S483.285,192,448,192z"/> + <path d="M448,384c-35.285,0-64,28.715-64,64c0,35.285,28.715,64,64,64c35.285,0,64-28.715,64-64C512,412.715,483.285,384,448,384 + z"/> + </g> + </g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> diff --git a/images/icons/icon-keypad-24-2x.png b/images/icons/icon-keypad-24-2x.png deleted file mode 100644 index 2b84863e648cf02bb13cbbdbe2e53168897fc5cd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4183 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%0AO*cUbm$P+wQJWN)N9qMl|R?1UAuN? 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 diff --git a/images/icons/icon-keypad-24.png b/images/icons/icon-keypad-24.png deleted file mode 100644 index 2abc9be80cb8393a2357c49383663951b5a4b88f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmV-#1CacQP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0009vNkl<Zc-oa# zOKVeM5N)Gva{EY3Y}1EHD<mKUyVI2jDp=`8e?z+|t!}bV5Wy656GAs#NP~+Ol0|4C zNI}F!7Dn=D)lD{qw)@0Q$ip<0B&~D8d~v)@)AquFX70?K`OY^pw_RQTo^FPLaYf+j z1t9%Zj&7OUpi9<6=oK(_fb{DTgobv>RWcJnBlKMY8pjz0QInvV-0Ssf)oRsOuh+jF z9v*&JSy^$A-3;vS?>{*@I{E_a?d?q~`||R#lYCcewOYJfE-%f@%$S+X#KpF%@1^8# zPsBsNKat(Gv9U2lKP$?9e0=;T91c&>yX)k$C1Z-kqL1u@f}}}+qZm=SbZ#^nC9>NO z4i4r|bPt_kp5Bd+Pg&aOgx|?-5hTs(p_d7==$unzAB#q#kH9CnH#avQ(7SOu!H$e6 z6beC{nkDR41kg%@=8y$dA^H0H`aQDWAUNn0uSn+{AxqW2B%2dhTU)zNG24itXKQhB z(M|Rt!ZpIce*}Ciu-j2NCr6@*3&6?#ql96CmGiX-pqCjJ3e6!4D(B!yx7+PXr_*z( zRB9G~!=^wNKAp*AUIYSxyIfd%0<@y&mIh=2fyd)<)BXHXI@8}jTrL-+xVgK#`$pXl zbyp=Gk3VAtY^Mm&<MP6wIS5d-zKarx#4HG?^L|TvrBeBN8G`J21SsOd1ej0^k&=kJ zWdfQ*79r7&km$9PC>D!7J(B>J9En73V~DuVRR>KXVKSMVL***DLMRk^OqcxJ@Au!q zY;+{x7bEQ0B9IB&V3PeD9SN|wuv;)Rm{1InB0#s`LIKF4k_Usqha}lg#FR><&kG9+ zx6UM>3tH7JyKNAl&-JSUvVa)LWlTPw|HyN2ia^84ZL{p0&{y9@uz_5^?5L<jyFtUU z(`c9Yq%(F4#vc=k4iW3R!WZg*EHWmW&A!7L*YBd{HpRz+_W2T4F18{sF6@>uOelIB z;8w(Cjze?Eg35Eb+<Th(QT;Y;eGj;k2ASQ)TXC3S?*Qd9#Bmm`UX!299{vI;5(^Vq zIp{oC+272``?%=VM_+yL6-hrk4hoptK)xT~I8FVx@fQ{IB2X&}y<-3X002ovPDHLk FV1j|#pF98n diff --git a/qml.qrc b/qml.qrc index 5d20bbbed..d9e8085f7 100644 --- a/qml.qrc +++ b/qml.qrc @@ -102,5 +102,6 @@ <file>src/commoncomponents/MaterialButton.qml</file> <file>src/mainview/components/RecordBox.qml</file> <file>src/commoncomponents/ElidedTextLabel.qml</file> + <file>src/mainview/components/SipInputPanel.qml</file> </qresource> </RCC> diff --git a/ressources.qrc b/ressources.qrc index f64f592a1..357fe0bf7 100644 --- a/ressources.qrc +++ b/ressources.qrc @@ -44,6 +44,7 @@ <file>images/icons/ic_content_copy.svg</file> <file>images/icons/ic_delete_black_18dp_2x.png</file> <file>images/icons/ic_done_white_24dp.png</file> + <file>images/icons/ic_keypad.svg</file> <file>images/icons/open_in_full-24px.svg</file> <file>images/icons/close_fullscreen-24px.svg</file> <file>images/icons/ic_group_add_white_24dp.png</file> @@ -97,9 +98,7 @@ <file>images/icons/screen_share-24px.svg</file> <file>images/icons/round-add_a_photo-24px.svg</file> <file>images/icons/ic_mic_white_24dp.png</file> - <file>images/icons/icon-keypad-24.png</file> <file>images/icons/ic_play_white_24dp.png</file> - <file>images/icons/icon-keypad-24-2x.png</file> <file>images/icons/ic_voicemail_black_24dp_2x_.png</file> <file>images/icons/av_icons/delete-24px.svg</file> <file>images/icons/av_icons/fiber_manual_record-24px.svg</file> diff --git a/src/calladapter.cpp b/src/calladapter.cpp index efc74102d..c4badc91d 100644 --- a/src/calladapter.cpp +++ b/src/calladapter.cpp @@ -27,7 +27,7 @@ #include "globalsystemtray.h" #include "utils.h" -CallAdapter::CallAdapter(QObject *parent) +CallAdapter::CallAdapter(QObject* parent) : QmlAdapterBase(parent) , oneSecondTimer_(new QTimer(this)) {} @@ -78,7 +78,7 @@ CallAdapter::placeCall() } void -CallAdapter::hangUpACall(const QString &accountId, const QString &convUid) +CallAdapter::hangUpACall(const QString& accountId, const QString& convUid) { auto* convModel = LRCInstance::getCurrentConversationModel(); const auto convInfo = convModel->getConversationForUID(convUid); @@ -88,7 +88,7 @@ CallAdapter::hangUpACall(const QString &accountId, const QString &convUid) } void -CallAdapter::refuseACall(const QString &accountId, const QString &convUid) +CallAdapter::refuseACall(const QString& accountId, const QString& convUid) { auto* convModel = LRCInstance::getCurrentConversationModel(); const auto convInfo = convModel->getConversationForUID(convUid); @@ -98,20 +98,20 @@ CallAdapter::refuseACall(const QString &accountId, const QString &convUid) } void -CallAdapter::acceptACall(const QString &accountId, const QString &convUid) +CallAdapter::acceptACall(const QString& accountId, const QString& convUid) { emit incomingCallNeedToSetupMainView(accountId, convUid); auto* convModel = LRCInstance::getCurrentConversationModel(); const auto convInfo = convModel->getConversationForUID(convUid); if (!convInfo.uid.isEmpty()) { LRCInstance::getAccountInfo(accountId).callModel->accept(convInfo.callId); - auto &accInfo = LRCInstance::getAccountInfo(convInfo.accountId); + auto& accInfo = LRCInstance::getAccountInfo(convInfo.accountId); accInfo.callModel->setCurrentCall(convInfo.callId); } } void -CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversation::Info &convInfo) +CallAdapter::slotShowIncomingCallView(const QString& accountId, const conversation::Info& convInfo) { auto* callModel = LRCInstance::getCurrentCallModel(); @@ -119,17 +119,17 @@ CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversati /* * Connection to close potential incoming call page when it is not current account. */ - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId); + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId); QObject::disconnect(closeIncomingCallPageConnection_); closeIncomingCallPageConnection_ = QObject::connect(accInfo.callModel.get(), &lrc::api::NewCallModel::callStatusChanged, - [this, accountId, uid = convInfo.uid](const QString &callId) { - auto &accInfo = LRCInstance::accountModel().getAccountInfo( + [this, accountId, uid = convInfo.uid](const QString& callId) { + auto& accInfo = LRCInstance::accountModel().getAccountInfo( accountId); - auto &callModel = accInfo.callModel; + auto& callModel = accInfo.callModel; auto call = callModel->getCall(callId); switch (call.status) { @@ -182,18 +182,18 @@ CallAdapter::slotShowIncomingCallView(const QString &accountId, const conversati } void -CallAdapter::slotShowCallView(const QString &accountId, const lrc::api::conversation::Info &convInfo) +CallAdapter::slotShowCallView(const QString& accountId, const lrc::api::conversation::Info& convInfo) { updateCall(convInfo.uid, accountId); } void -CallAdapter::updateCall(const QString &convUid, const QString &accountId, bool forceCallOnly) +CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool forceCallOnly) { accountId_ = accountId.isEmpty() ? accountId_ : accountId; convUid_ = convUid.isEmpty() ? convUid_ : convUid; - auto *convModel = LRCInstance::getCurrentConversationModel(); + auto* convModel = LRCInstance::getCurrentConversationModel(); const auto convInfo = convModel->getConversationForUID(convUid_); if (convInfo.uid.isEmpty()) { return; @@ -223,7 +223,7 @@ CallAdapter::updateCall(const QString &convUid, const QString &accountId, bool f bool CallAdapter::shouldShowPreview(bool force) { - bool shouldShowPreview{false}; + bool shouldShowPreview {false}; auto* convModel = LRCInstance::getCurrentConversationModel(); const auto convInfo = convModel->getConversationForUID(convUid_); if (convInfo.uid.isEmpty()) { @@ -245,11 +245,11 @@ CallAdapter::getConferencesInfos() const auto convInfo = convModel->getConversationForUID(convUid_); if (convInfo.uid.isEmpty()) return map; - auto callId = convInfo.confId.isEmpty()? convInfo.callId : convInfo.confId; + auto callId = convInfo.confId.isEmpty() ? convInfo.callId : convInfo.confId; if (!callId.isEmpty()) { try { auto call = LRCInstance::getCurrentCallModel()->getCall(callId); - for (const auto& participant: call.participantsInfos) { + for (const auto& participant : call.participantsInfos) { QJsonObject data; data["x"] = participant["x"].toInt(); data["y"] = participant["y"].toInt(); @@ -257,31 +257,34 @@ CallAdapter::getConferencesInfos() data["h"] = participant["h"].toInt(); data["active"] = participant["active"] == "true"; auto bestName = participant["uri"]; - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); data["isLocal"] = false; if (bestName == accInfo.profileInfo.uri) { bestName = tr("me"); data["isLocal"] = true; } else { try { - auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]); + auto& contact = LRCInstance::getCurrentAccountInfo() + .contactModel->getContact(participant["uri"]); bestName = Utils::bestNameForContact(contact); - } catch (...) {} + } catch (...) { + } } data["bestName"] = bestName; map.push_back(QVariant(data)); } return map; - } catch (...) {} + } catch (...) { + } } return map; } void -CallAdapter::connectCallModel(const QString &accountId) +CallAdapter::connectCallModel(const QString& accountId) { - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId); + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId); QObject::disconnect(callStatusChangedConnection_); QObject::disconnect(onParticipantsChangedConnection_); @@ -289,15 +292,15 @@ CallAdapter::connectCallModel(const QString &accountId) onParticipantsChangedConnection_ = QObject::connect( accInfo.callModel.get(), &lrc::api::NewCallModel::onParticipantsChanged, - [this, accountId](const QString &confId) { - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId); - auto &callModel = accInfo.callModel; + [this, accountId](const QString& confId) { + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId); + auto& callModel = accInfo.callModel; auto call = callModel->getCall(confId); const auto convInfo = LRCInstance::getConversationFromCallId(confId); if (!convInfo.uid.isEmpty()) { // Convert to QML QVariantList map; - for (const auto& participant: call.participantsInfos) { + for (const auto& participant : call.participantsInfos) { QJsonObject data; data["x"] = participant["x"].toInt(); data["y"] = participant["y"].toInt(); @@ -307,29 +310,31 @@ CallAdapter::connectCallModel(const QString &accountId) data["active"] = participant["active"] == "true"; auto bestName = participant["uri"]; data["isLocal"] = false; - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); if (bestName == accInfo.profileInfo.uri) { bestName = tr("me"); data["isLocal"] = true; } else { try { - auto &contact = LRCInstance::getCurrentAccountInfo().contactModel->getContact(participant["uri"]); + auto& contact = LRCInstance::getCurrentAccountInfo() + .contactModel->getContact(participant["uri"]); bestName = Utils::bestNameForContact(contact); - } catch (...) {} + } catch (...) { + } } data["bestName"] = bestName; map.push_back(QVariant(data)); } emit updateParticipantsInfos(map, accountId, confId); } - }); + }); callStatusChangedConnection_ = QObject::connect( accInfo.callModel.get(), &lrc::api::NewCallModel::callStatusChanged, - [this, accountId](const QString &callId) { - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId); - auto &callModel = accInfo.callModel; + [this, accountId](const QString& callId) { + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId); + auto& callModel = accInfo.callModel; const auto call = callModel->getCall(callId); /* @@ -357,7 +362,7 @@ CallAdapter::connectCallModel(const QString &accountId) * If it's a conference, change the smartlist index * to the next remaining participant. */ - bool forceCallOnly{false}; + bool forceCallOnly {false}; if (!convInfo.confId.isEmpty()) { auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId); if (callList.empty()) { @@ -366,7 +371,7 @@ CallAdapter::connectCallModel(const QString &accountId) callList.append(lastConferencee); forceCallOnly = true; } - for (const auto &callId : callList) { + for (const auto& callId : callList) { if (!callModel->hasCall(callId)) { continue; } @@ -408,17 +413,28 @@ CallAdapter::connectCallModel(const QString &accountId) }); } +void +CallAdapter::sipInputPanelPlayDTMF(const QString& key) +{ + auto callId = LRCInstance::getCallIdForConversationUid(convUid_, accountId_); + if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) { + return; + } + + LRCInstance::getCurrentCallModel()->playDTMF(callId, key); +} + /* * For Call Overlay */ void -CallAdapter::updateCallOverlay(const lrc::api::conversation::Info &convInfo) +CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo) { setTime(accountId_, convUid_); QObject::disconnect(oneSecondTimer_); QObject::connect(oneSecondTimer_, &QTimer::timeout, [this] { setTime(accountId_, convUid_); }); oneSecondTimer_->start(20); - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); auto call = LRCInstance::getCallInfoForConversation(convInfo); if (!call) { @@ -450,13 +466,13 @@ CallAdapter::hangupCall(const QString& uri) auto callModel = LRCInstance::getAccountInfo(accountId_).callModel.get(); if (callModel->hasCall(convInfo.callId)) { /* - * Store the last remaining participant of the conference, - * so we can switch the smartlist index after termination. - */ + * Store the last remaining participant of the conference, + * so we can switch the smartlist index after termination. + */ if (!convInfo.confId.isEmpty()) { auto callList = LRCInstance::getAPI().getConferenceSubcalls(convInfo.confId); if (callList.size() == 2) { - for (const auto &cId : callList) { + for (const auto& cId : callList) { if (cId != convInfo.callId) { LRCInstance::instance().pushLastConferencee(convInfo.confId, cId); } @@ -487,21 +503,23 @@ CallAdapter::maximizeParticipant(const QString& uri, bool isActive) try { const auto call = callModel->getCall(confId); switch (call.layout) { - case lrc::api::call::Layout::GRID: - callModel->setActiveParticipant(confId, callId); - callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); - break; - case lrc::api::call::Layout::ONE_WITH_SMALL: - callModel->setActiveParticipant(confId, callId); - callModel->setConferenceLayout(confId, - isActive? lrc::api::call::Layout::ONE : lrc::api::call::Layout::ONE_WITH_SMALL); - break; - case lrc::api::call::Layout::ONE: - callModel->setActiveParticipant(confId, callId); - callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); - break; + case lrc::api::call::Layout::GRID: + callModel->setActiveParticipant(confId, callId); + callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); + break; + case lrc::api::call::Layout::ONE_WITH_SMALL: + callModel->setActiveParticipant(confId, callId); + callModel->setConferenceLayout(confId, + isActive ? lrc::api::call::Layout::ONE + : lrc::api::call::Layout::ONE_WITH_SMALL); + break; + case lrc::api::call::Layout::ONE: + callModel->setActiveParticipant(confId, callId); + callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); + break; }; - } catch (...) {} + } catch (...) { + } } void @@ -514,16 +532,17 @@ CallAdapter::minimizeParticipant() try { auto call = callModel->getCall(confId); switch (call.layout) { - case lrc::api::call::Layout::GRID: - break; - case lrc::api::call::Layout::ONE_WITH_SMALL: - callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); - break; - case lrc::api::call::Layout::ONE: - callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); - break; + case lrc::api::call::Layout::GRID: + break; + case lrc::api::call::Layout::ONE_WITH_SMALL: + callModel->setConferenceLayout(confId, lrc::api::call::Layout::GRID); + break; + case lrc::api::call::Layout::ONE: + callModel->setConferenceLayout(confId, lrc::api::call::Layout::ONE_WITH_SMALL); + break; }; - } catch (...) {} + } catch (...) { + } } void @@ -544,11 +563,11 @@ CallAdapter::hangUpThisCall() bool CallAdapter::isRecordingThisCall() { - auto &accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); - auto &convModel = accInfo.conversationModel; + auto& accInfo = LRCInstance::accountModel().getAccountInfo(accountId_); + auto& convModel = accInfo.conversationModel; const auto convInfo = convModel->getConversationForUID(convUid_); return accInfo.callModel->isRecording(convInfo.confId) - || accInfo.callModel->isRecording(convInfo.callId); + || accInfo.callModel->isRecording(convInfo.callId); } bool @@ -565,7 +584,8 @@ CallAdapter::isCurrentMaster() const auto call = callModel->getCall(convInfo.callId); return call.participantsInfos.size() == 0; } - } catch (...) {} + } catch (...) { + } } return true; } @@ -580,7 +600,8 @@ CallAdapter::getCurrentLayoutType() const try { auto call = callModel->getCall(convInfo.confId); return Utils::toUnderlyingValue(call.layout); - } catch (...) {} + } catch (...) { + } } return -1; } @@ -640,7 +661,7 @@ CallAdapter::videoPauseThisCallToggle() } void -CallAdapter::setTime(const QString &accountId, const QString &convUid) +CallAdapter::setTime(const QString& accountId, const QString& convUid) { const auto callId = LRCInstance::getCallIdForConversationUid(convUid, accountId); if (callId.isEmpty() || !LRCInstance::getCurrentCallModel()->hasCall(callId)) { @@ -652,4 +673,4 @@ CallAdapter::setTime(const QString &accountId, const QString &convUid) auto timeString = LRCInstance::getCurrentCallModel()->getFormattedCallDuration(callId); emit updateTimeText(timeString); } -} +} \ No newline at end of file diff --git a/src/calladapter.h b/src/calladapter.h index ce439731b..fbf5ffc4d 100644 --- a/src/calladapter.h +++ b/src/calladapter.h @@ -31,7 +31,7 @@ class CallAdapter : public QmlAdapterBase Q_OBJECT public: - explicit CallAdapter(QObject *parent = nullptr); + explicit CallAdapter(QObject* parent = nullptr); ~CallAdapter(); /* @@ -41,11 +41,12 @@ public: Q_INVOKABLE void placeAudioOnlyCall(); Q_INVOKABLE void placeCall(); - Q_INVOKABLE void hangUpACall(const QString &accountId, const QString &convUid); - Q_INVOKABLE void refuseACall(const QString &accountId, const QString &convUid); - Q_INVOKABLE void acceptACall(const QString &accountId, const QString &convUid); + Q_INVOKABLE void hangUpACall(const QString& accountId, const QString& convUid); + Q_INVOKABLE void refuseACall(const QString& accountId, const QString& convUid); + Q_INVOKABLE void acceptACall(const QString& accountId, const QString& convUid); - Q_INVOKABLE void connectCallModel(const QString &accountId); + Q_INVOKABLE void connectCallModel(const QString& accountId); + Q_INVOKABLE void sipInputPanelPlayDTMF(const QString& key); /* * For Call Overlay @@ -55,7 +56,7 @@ public: Q_INVOKABLE void minimizeParticipant(); Q_INVOKABLE void hangUpThisCall(); Q_INVOKABLE bool isCurrentMaster() const; - Q_INVOKABLE int getCurrentLayoutType() const; + Q_INVOKABLE int getCurrentLayoutType() const; Q_INVOKABLE void holdThisCallToggle(); Q_INVOKABLE void muteThisCallToggle(); Q_INVOKABLE void recordThisCallToggle(); @@ -64,24 +65,26 @@ public: Q_INVOKABLE QVariantList getConferencesInfos(); signals: - void showOutgoingCallPage(const QString &accountId, const QString &convUid); - void showIncomingCallPage(const QString &accountId, const QString &convUid); - void showAudioCallPage(const QString &accountId, const QString &convUid); - void showVideoCallPage(const QString &accountId, const QString &convUid, const QString &callId); - void showCallStack(const QString &accountId, const QString &convUid, bool forceReset = false); - void closeCallStack(const QString &accountId, const QString &convUid); - void closePotentialIncomingCallPageWindow(const QString &accountId, const QString &convUid); - void callStatusChanged(const QString &status, const QString &accountId, const QString &convUid); + void showOutgoingCallPage(const QString& accountId, const QString& convUid); + void showIncomingCallPage(const QString& accountId, const QString& convUid); + void showAudioCallPage(const QString& accountId, const QString& convUid); + void showVideoCallPage(const QString& accountId, const QString& convUid, const QString& callId); + void showCallStack(const QString& accountId, const QString& convUid, bool forceReset = false); + void closeCallStack(const QString& accountId, const QString& convUid); + void closePotentialIncomingCallPageWindow(const QString& accountId, const QString& convUid); + void callStatusChanged(const QString& status, const QString& accountId, const QString& convUid); void updateConversationSmartList(); - void updateParticipantsInfos(const QVariantList& infos, const QString &accountId, const QString &callId); + void updateParticipantsInfos(const QVariantList& infos, + const QString& accountId, + const QString& callId); - void incomingCallNeedToSetupMainView(const QString &accountId, const QString &convUid); + void incomingCallNeedToSetupMainView(const QString& accountId, const QString& convUid); void previewVisibilityNeedToChange(bool visible); /* * For Call Overlay */ - void updateTimeText(const QString &time); + void updateTimeText(const QString& time); void showOnHoldLabel(bool isPaused); void updateOverlay(bool isPaused, bool isAudioOnly, @@ -90,17 +93,17 @@ signals: bool isRecording, bool isSIP, bool isConferenceCall, - const QString &bestName); + const QString& bestName); public slots: - void slotShowIncomingCallView(const QString &accountId, - const lrc::api::conversation::Info &convInfo); - void slotShowCallView(const QString &accountId, const lrc::api::conversation::Info &convInfo); + void slotShowIncomingCallView(const QString& accountId, + const lrc::api::conversation::Info& convInfo); + void slotShowCallView(const QString& accountId, const lrc::api::conversation::Info& convInfo); void slotAccountChanged(); private: - void updateCall(const QString &convUid = {}, - const QString &accountId = {}, + void updateCall(const QString& convUid = {}, + const QString& accountId = {}, bool forceCallOnly = false); bool shouldShowPreview(bool force); @@ -117,7 +120,7 @@ private: /* * For Call Overlay */ - void updateCallOverlay(const lrc::api::conversation::Info &convInfo); - void setTime(const QString &accountId, const QString &convUid); - QTimer *oneSecondTimer_; + void updateCallOverlay(const lrc::api::conversation::Info& convInfo); + void setTime(const QString& accountId, const QString& convUid); + QTimer* oneSecondTimer_; }; diff --git a/src/commoncomponents/HoverableButton.qml b/src/commoncomponents/HoverableButton.qml index 4e96677d2..0668487b9 100644 --- a/src/commoncomponents/HoverableButton.qml +++ b/src/commoncomponents/HoverableButton.qml @@ -28,6 +28,8 @@ import QtGraphicalEffects 1.15 * 2. Radius control (rounded) * 3. Text content or image content * 4. Can use OnClicked slot to implement some click logic + * + * Note: if use text property directly, buttonTextColor will not work. */ Button { id: hoverableButton @@ -39,6 +41,9 @@ Button { property int buttonImageHeight: hoverableButtonBackground.height - 10 property int buttonImageWidth: hoverableButtonBackground.width - 10 + property string buttonText: "" + property string buttonTextColor: "black" + property string backgroundColor: JamiTheme.releaseColor property string onPressColor: JamiTheme.pressColor property string onReleaseColor: JamiTheme.releaseColor @@ -58,6 +63,8 @@ Button { hoverEnabled: true + text: "<font color=" + "'" + buttonTextColor + "'>" + buttonText + "</font>" + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.visible: hovered && (toolTipText.length > 0) ToolTip.text: toolTipText @@ -79,7 +86,12 @@ Button { mipmap: true asynchronous: true - source: hoverableButton.checked && checkedImage? checkedImage : baseImage + source: { + if (checkable && checkedImage) + return hoverableButton.checked ? checkedImage : baseImage + else + return "" + } layer { enabled: true diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index a29330de1..d9086c69c 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -68,6 +68,9 @@ Item { property string draftRed: "#cf5300" + property string sipInputButtonBackgroundColor: "#336699" + property string sipInputButtonHoverColor: "#4477aa" + property string sipInputButtonPressColor: "#5588bb" /* * Font. diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml index 75774d34f..1f2ed4ede 100644 --- a/src/mainview/components/CallOverlay.qml +++ b/src/mainview/components/CallOverlay.qml @@ -110,6 +110,12 @@ Rectangle { anchors.fill: parent + SipInputPanel { + id: sipInputPanel + + x: callOverlayRect.width / 2 - sipInputPanel.width / 2 + y: callOverlayRect.height / 2 - sipInputPanel.height / 2 + } /* * Timer to decide when overlay fade out. diff --git a/src/mainview/components/CallOverlayButtonGroup.qml b/src/mainview/components/CallOverlayButtonGroup.qml index c360a7cda..107ab9e25 100644 --- a/src/mainview/components/CallOverlayButtonGroup.qml +++ b/src/mainview/components/CallOverlayButtonGroup.qml @@ -50,8 +50,6 @@ Rectangle { root.isSip = isSIP noVideoButton.visible = !isAudioOnly addToConferenceButton.visible = !isSIP && isMaster - transferCallButton.visible = isSIP - sipInputPanelButton.visible = isSIP noMicButton.checked = isAudioMuted noVideoButton.checked = isVideoMuted diff --git a/src/mainview/components/CallViewContextMenu.qml b/src/mainview/components/CallViewContextMenu.qml index 23da3fd70..2deeeaac2 100644 --- a/src/mainview/components/CallViewContextMenu.qml +++ b/src/mainview/components/CallViewContextMenu.qml @@ -112,6 +112,23 @@ Menu { } } + GeneralMenuItem { + id: showSipInputPanelButton + + visible: isSIP + height: isSIP? undefined : 0 + + itemName: qsTr("Sip Input Panel") + iconSource: "qrc:/images/icons/ic_keypad.svg" + leftBorderWidth: commonBorderWidth + rightBorderWidth: commonBorderWidth + + onClicked: { + root.close() + sipInputPanel.open() + } + } + GeneralMenuItem { id: transferCallButton diff --git a/src/mainview/components/SipInputPanel.qml b/src/mainview/components/SipInputPanel.qml new file mode 100644 index 000000000..8d547a26a --- /dev/null +++ b/src/mainview/components/SipInputPanel.qml @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQuick.Controls.Universal 2.12 +import net.jami.Models 1.0 + +import "../../commoncomponents" + +/* + * SipInputPanel is a key pad that is designed to be + * used in sip calls. + */ +Popup { + id: sipInputPanelPopUp + + /* + * Space between sipInputPanelRect and grid layout + */ + property int sipPanelPadding: 20 + + contentWidth: sipInputPanelRectGridLayout.implicitWidth + 20 + contentHeight: sipInputPanelRectGridLayout.implicitHeight + 20 + + padding: 0 + + modal: true + + contentItem: Rectangle { + id: sipInputPanelRect + + radius: 10 + + GridLayout { + id: sipInputPanelRectGridLayout + + anchors.centerIn: parent + + columns: 4 + + Repeater { + id: sipInputPanelRectGridLayoutRepeater + model: ["1", "2", "3", "A", "4", "5", "6", "B", "7", + "8", "9", "C", "*", "0", "#", "D"] + + HoverableButton { + id: sipInputPanelButton + + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + + radius: 30 + buttonText: modelData + buttonTextColor: "white" + checkable: false + backgroundColor: JamiTheme.sipInputButtonBackgroundColor + onEnterColor: JamiTheme.sipInputButtonHoverColor + onExitColor: JamiTheme.sipInputButtonBackgroundColor + onPressColor: JamiTheme.sipInputButtonPressColor + onReleaseColor: JamiTheme.sipInputButtonHoverColor + + toolTipText: modelData + + onClicked: { + CallAdapter.sipInputPanelPlayDTMF(modelData) + } + } + } + } + } + + background: Rectangle { + color: "transparent" + } +} -- GitLab