diff --git a/images/icons/add_people_black_24dp.svg b/images/icons/add_people_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..2bc314f7d92022393cd40ac5782472ad40063b05 --- /dev/null +++ b/images/icons/add_people_black_24dp.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<path d="M16.1,11.3c1.1-0.7,1.8-2,1.8-3.3c0-2.2-1.8-4-4-4c-2.2,0-4,1.8-4,4c0,1.3,0.7,2.6,1.8,3.3c-0.6,0.3-1.2,0.6-1.8,1.1 + c-0.3-0.3-0.6-0.5-1-0.7c0.6-0.6,1-1.4,1-2.3c0-1.8-1.4-3.2-3.2-3.2c-1.8,0-3.2,1.4-3.2,3.2c0,0.9,0.4,1.7,1,2.3 + c-1.5,0.8-2.5,2.4-2.5,4.2c0,0.6,0.5,1.1,1.1,1.1h4.7c0,0.6,0.5,1.1,1.1,1.1h6.5l-0.3-0.2c-0.3-0.2-0.5-0.6-0.7-1l0-0.1H9.1 + c0-0.3,0.1-0.7,0.1-1c0.1-0.6,0.4-1.1,0.7-1.6c0.2-0.3,0.4-0.6,0.7-0.8c0.9-0.8,2.1-1.3,3.3-1.3c1.1,0,2.1,0.4,3,1l0.1,0.1l0.1-0.1 + c0.1-0.2,0.2-0.4,0.4-0.6c0.1-0.1,0.1-0.1,0.2-0.2l0.1-0.1l-0.1-0.1C17.2,11.8,16.6,11.5,16.1,11.3z M16.6,7.9 + c0,1.5-1.2,2.7-2.7,2.7c-1.5,0-2.7-1.2-2.7-2.7c0-1.5,1.2-2.7,2.7-2.7C15.4,5.2,16.6,6.4,16.6,7.9z M6.6,11.3c-1.1,0-2-0.9-2-2 + c0-1.1,0.9-2,2-2c1.1,0,2,0.9,2,2C8.6,10.4,7.7,11.3,6.6,11.3z M9,13.3c-0.5,0.7-0.9,1.5-1.1,2.4H3.2c0.1-1.8,1.6-3.3,3.4-3.3 + C7.5,12.4,8.4,12.7,9,13.3z"/> +<path d="M21.2,15.6l-1.7,0l0-1.7c0-0.4-0.3-0.7-0.7-0.7c-0.2,0-0.4,0.1-0.5,0.2c-0.1,0.1-0.2,0.3-0.2,0.5l0,1.7l-1.7,0 + c-0.2,0-0.4,0.1-0.5,0.2c-0.1,0.1-0.2,0.3-0.2,0.5c0,0.4,0.3,0.7,0.7,0.7l1.7,0l0,1.7c0,0.4,0.3,0.7,0.7,0.7h0 + c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5l0-1.7l1.7,0c0.2,0,0.4-0.1,0.5-0.2c0.1-0.1,0.2-0.3,0.2-0.5 + C21.9,16,21.6,15.7,21.2,15.6z"/> +</svg> diff --git a/images/icons/chat_black_24dp.svg b/images/icons/chat_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..867ba122d8f89c57e0a98380218fc21e509b0f9a --- /dev/null +++ b/images/icons/chat_black_24dp.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<g id="Icones_Outline"> + <g id="Chat_Black_24dp"> + <g id="Shape" transform="translate(3.000000, 4.000000)"> + <g> + <path d="M4.6,17c-0.2,0-0.4,0-0.6-0.1c-0.6-0.3-0.9-0.8-0.9-1.5V13H2.3C0.5,13-1,11.5-1,9.6V2.3C-1,0.5,0.5-1,2.3-1h13.3 + C17.5-1,19,0.5,19,2.3v7.3c0,1.8-1.5,3.3-3.3,3.3H9.5l-3.8,3.6C5.4,16.8,5,17,4.6,17z M2.3,0.4c-1.1,0-1.9,0.9-1.9,2v7.3 + c0,1,0.8,1.9,1.9,1.9h2.2v3.9c0,0,0,0.1,0.1,0.1s0.1,0,0.2,0l4.2-4h6.7c1,0,1.9-0.8,1.9-1.9V2.3c0-1-0.8-1.9-1.9-1.9H2.3 + L2.3,0.4z"/> + </g> + </g> + </g> +</g> +</svg> diff --git a/images/icons/record_black_24dp.svg b/images/icons/record_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f5bb1ac5a714854b3d2ef8949e287816d8dded4 --- /dev/null +++ b/images/icons/record_black_24dp.svg @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20.3c-4.6,0-8.3-3.7-8.3-8.3S7.4,3.7,12,3.7 + s8.3,3.7,8.3,8.3S16.6,20.3,12,20.3z"/> +</svg> diff --git a/images/icons/share_screen_black_24dp.svg b/images/icons/share_screen_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..de0e6a11b1749dab59038fac2f54bb7ac9231036 --- /dev/null +++ b/images/icons/share_screen_black_24dp.svg @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<path id="Path" d="M12.6,8.9V7.8l2.6,2.2l-2.6,2.2V11c-3.4,0.1-4.2,2.6-4.2,2.6C8.5,9.6,11.7,9,12.6,8.9z"/> +<g id="Icones_Outline"> + <g id="Laptop_Black_24dp"> + <g transform="translate(2.000000, 5.000000)"> + <g id="Shape"> + <path d="M17,12H2.8c-0.5,0-0.9-0.2-1.2-0.5C1.2,11.1,1,10.7,1,10.2V1.3c0-0.5,0.2-0.9,0.5-1.2s0.8-0.5,1.2-0.5H17 + c0.5,0,0.9,0.2,1.2,0.5c0.3,0.3,0.5,0.8,0.5,1.2v8.9c0,0.5-0.2,0.9-0.5,1.2S17.5,12,17,12z M2.8,0.9C2.7,0.9,2.6,1,2.5,1 + c0,0.1-0.1,0.2-0.1,0.3v8.9c0,0.1,0,0.2,0.1,0.3c0,0,0.1,0.1,0.3,0.1H17c0.1,0,0.2-0.1,0.3-0.1c0.1-0.1,0.1-0.2,0.1-0.3V1.3 + c0-0.1,0-0.2-0.1-0.3c0,0-0.1-0.1-0.3-0.1C17,0.9,2.8,0.9,2.8,0.9z"/> + </g> + <g id="Line-2"> + <path d="M19.5,14.4h-19c-0.4,0-0.7-0.3-0.7-0.7S0.1,13,0.5,13h19c0.4,0,0.7,0.3,0.7,0.7S19.9,14.4,19.5,14.4z"/> + </g> + </g> + </g> +</g> +</svg> diff --git a/images/icons/spk_black_24dp.svg b/images/icons/spk_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..106a2711a23f287abb4ed8fd1969eeb06c665ba9 --- /dev/null +++ b/images/icons/spk_black_24dp.svg @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<g> + <path d="M14.6,4c-0.4-0.7-1.3-0.8-2-0.4L6.8,7.7H4.1C3,7.7,2,8.7,2,9.9v4.3c0,1.2,1,2.1,2.1,2.1h2.6l5.8,4.1 + c0.2,0.2,0.5,0.2,0.8,0.2c0.8,0,1.4-0.6,1.5-1.4V4.8C14.9,4.5,14.8,4.2,14.6,4z M13.4,19.2L13.4,19.2l-5.7-4l-0.4-0.3h-1H6.1h-2 + c-0.4,0-0.7-0.3-0.7-0.7V9.9c0-0.4,0.3-0.7,0.7-0.7H6h0.2h1l0.4-0.3l5.7-4L13.4,19.2L13.4,19.2z"/> + <path d="M19.1,12c0,1.5-0.6,2.9-1.6,4c-0.3,0.3-0.7,0.3-1,0c-0.3-0.3-0.3-0.7,0-1c1.6-1.7,1.6-4.3,0-6c-0.3-0.3-0.3-0.7,0-1 + c0.3-0.3,0.7-0.3,1,0C18.6,9.1,19.1,10.5,19.1,12z"/> + <path d="M18.9,5.1c-0.3-0.3-0.7-0.2-1,0c-0.3,0.3-0.2,0.7,0,1c3.3,2.9,3.5,8,0.6,11.2c-0.2,0.2-0.4,0.4-0.6,0.6 + c-0.3,0.3-0.3,0.7,0,1c0.3,0.3,0.7,0.3,1,0c3.8-3.5,4.1-9.4,0.7-13.2C19.4,5.5,19.1,5.3,18.9,5.1L18.9,5.1z"/> +</g> +</svg> diff --git a/images/icons/spk_none_black_24dp.svg b/images/icons/spk_none_black_24dp.svg new file mode 100644 index 0000000000000000000000000000000000000000..0c2c89b6bdeeee845715bb26dcbc0990796ac927 --- /dev/null +++ b/images/icons/spk_none_black_24dp.svg @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve"> +<g> + <path d="M14.6,4c-0.4-0.7-1.3-0.8-2-0.4L6.8,7.7H4.1C3,7.7,2,8.7,2,9.9v4.3c0,1.2,1,2.1,2.1,2.1h2.6l5.8,4.1 + c0.2,0.2,0.5,0.2,0.8,0.2c0.8,0,1.4-0.6,1.5-1.4V4.8C14.9,4.5,14.8,4.2,14.6,4z M13.4,19.2L13.4,19.2l-5.7-4l-0.4-0.3h-1H6.1h-2 + c-0.4,0-0.7-0.3-0.7-0.7V9.9c0-0.4,0.3-0.7,0.7-0.7H6h0.2h1l0.4-0.3l5.7-4L13.4,19.2L13.4,19.2z"/> + <path d="M19.1,12c0,1.5-0.6,2.9-1.6,4c-0.3,0.3-0.7,0.3-1,0c-0.3-0.3-0.3-0.7,0-1c1.6-1.7,1.6-4.3,0-6c-0.3-0.3-0.3-0.7,0-1 + c0.3-0.3,0.7-0.3,1,0C18.6,9.1,19.1,10.5,19.1,12z"/> + <path d="M18.9,5.1c-0.3-0.3-0.7-0.2-1,0c-0.3,0.3-0.2,0.7,0,1c3.3,2.9,3.5,8,0.6,11.2c-0.2,0.2-0.4,0.4-0.6,0.6 + c-0.3,0.3-0.3,0.7,0,1c0.3,0.3,0.7,0.3,1,0c3.8-3.5,4.1-9.4,0.7-13.2C19.4,5.5,19.1,5.3,18.9,5.1L18.9,5.1z"/> +</g> +<g> + <path d="M3.3,20.7c-0.2,0-0.3-0.1-0.5-0.2c-0.3-0.3-0.3-0.7-0.1-1L16.3,3.7c0.3-0.3,0.7-0.3,1-0.1c0.3,0.3,0.3,0.7,0.1,1L3.8,20.5 + C3.7,20.6,3.5,20.7,3.3,20.7z"/> +</g> +</svg> diff --git a/qml.qrc b/qml.qrc index 5b974ba7e5de68a4f0d5f8855f3f95fc2fc2e0b4..80b9a37d1c914f703e4ac31fa3f436e05f4201e5 100644 --- a/qml.qrc +++ b/qml.qrc @@ -106,7 +106,6 @@ <file>src/mainview/components/CallStackView.qml</file> <file>src/mainview/components/InitialCallPage.qml</file> <file>src/mainview/components/CallOverlay.qml</file> - <file>src/mainview/components/CallOverlayButtonGroup.qml</file> <file>src/mainview/components/ContactSearchBar.qml</file> <file>src/mainview/components/OngoingCallPage.qml</file> <file>src/mainview/components/ParticipantOverlay.qml</file> @@ -137,5 +136,10 @@ <file>src/mainview/components/SmartListItemDelegate.qml</file> <file>src/mainview/components/BadgeNotifier.qml</file> <file>src/mainview/components/ParticipantsLayer.qml</file> + <file>src/mainview/components/MainOverlay.qml</file> + <file>src/mainview/components/CallButtonDelegate.qml</file> + <file>src/mainview/components/CallActionBar.qml</file> + <file>src/commoncomponents/HalfPill.qml</file> + <file>src/commoncomponents/MaterialToolTip.qml</file> </qresource> </RCC> diff --git a/resources.qrc b/resources.qrc index 0766587550e62cd8cacc39f26324345609af293c..bbfe9d5062827485754194bac0e29be2e12daea0 100644 --- a/resources.qrc +++ b/resources.qrc @@ -134,5 +134,11 @@ <file>images/icons/settings-24px.svg</file> <file>images/icons/quote.svg</file> <file>images/icons/plugins-24px.svg</file> + <file>images/icons/record_black_24dp.svg</file> + <file>images/icons/share_screen_black_24dp.svg</file> + <file>images/icons/chat_black_24dp.svg</file> + <file>images/icons/add_people_black_24dp.svg</file> + <file>images/icons/spk_black_24dp.svg</file> + <file>images/icons/spk_none_black_24dp.svg</file> </qresource> </RCC> diff --git a/src/audiodevicemodel.cpp b/src/audiodevicemodel.cpp index 9dd71c82a22e4702c5debc4bde42fefbf4abb6b4..9bb6e7d80f19e8453d8f7dcfc808504aca8c1359 100644 --- a/src/audiodevicemodel.cpp +++ b/src/audiodevicemodel.cpp @@ -62,6 +62,8 @@ AudioDeviceModel::data(const QModelIndex& index, int role) const } case Role::RawDeviceName: return QVariant(devices_.at(index.row())); + case Role::isCurrent: + return QVariant(index.row() == getCurrentIndex()); default: break; } @@ -115,7 +117,7 @@ AudioDeviceModel::reset() } int -AudioDeviceModel::getCurrentIndex() +AudioDeviceModel::getCurrentIndex() const { QString currentId = lrcInstance_->avModel().getInputDevice(); auto resultList = match(index(0, 0), Qt::DisplayRole, QVariant(currentId)); diff --git a/src/audiodevicemodel.h b/src/audiodevicemodel.h index e8cf37e9bb62547dd9ba71879a71544a4c368a4b..11a966cbb2b1002d9657d6bf584f504a4cc398ef 100644 --- a/src/audiodevicemodel.h +++ b/src/audiodevicemodel.h @@ -28,7 +28,7 @@ public: Q_ENUM(Type) Q_PROPERTY(Type type MEMBER type_ NOTIFY typeChanged) - enum Role { DeviceName = Qt::UserRole + 1, RawDeviceName }; + enum Role { DeviceName = Qt::UserRole + 1, RawDeviceName, isCurrent }; Q_ENUM(Role) Q_SIGNALS: @@ -56,10 +56,10 @@ public: Qt::ItemFlags flags(const QModelIndex& index) const override; Q_INVOKABLE void reset(); - Q_INVOKABLE int getCurrentIndex(); + Q_INVOKABLE int getCurrentIndex() const; private: QVector<QString> devices_; Type type_ {Type::Invalid}; -}; \ No newline at end of file +}; diff --git a/src/avadapter.cpp b/src/avadapter.cpp index b9101139fcfb297d1e88ba1eff017a9b8594a506..d8d555ed752f2fa0c7bd52e83ebdda59ad38fcb6 100644 --- a/src/avadapter.cpp +++ b/src/avadapter.cpp @@ -69,13 +69,19 @@ AvAdapter::populateVideoDeviceContextMenuItem() } void -AvAdapter::onVideoContextMenuDeviceItemClicked(const QString& deviceName) +AvAdapter::selectVideoInputDeviceByName(const QString& deviceName) { auto deviceId = lrcInstance_->avModel().getDeviceIdFromName(deviceName); if (deviceId.isEmpty()) { qWarning() << "Couldn't find device: " << deviceName; return; } + selectVideoInputDeviceById(deviceId); +} + +void +AvAdapter::selectVideoInputDeviceById(const QString& deviceId) +{ lrcInstance_->avModel().setCurrentVideoCaptureDevice(deviceId); lrcInstance_->avModel().switchInputTo(deviceId, getCurrentCallId()); } diff --git a/src/avadapter.h b/src/avadapter.h index ff6a56046127113cfc13e1fc874c32c5b77772d3..c0347999f9fbb8eecaf90caa677151d1bca84c94 100644 --- a/src/avadapter.h +++ b/src/avadapter.h @@ -45,8 +45,11 @@ protected: // Return needed info for populating video device context menu item. Q_INVOKABLE QVariantMap populateVideoDeviceContextMenuItem(); - // Preview video input switching. - Q_INVOKABLE void onVideoContextMenuDeviceItemClicked(const QString& deviceName); + // switch preview video input by device name + Q_INVOKABLE void selectVideoInputDeviceByName(const QString& deviceName); + + // switch preview video input by device id + Q_INVOKABLE void selectVideoInputDeviceById(const QString& deviceId); // Share the screen specificed by screen number. Q_INVOKABLE void shareEntireScreen(int screenNumber); diff --git a/src/calladapter.cpp b/src/calladapter.cpp index 22fc61bb6012c3551622aae93211b4fb73c84176..57f70c1bb898469410ba337fac1457e92e056bac 100644 --- a/src/calladapter.cpp +++ b/src/calladapter.cpp @@ -639,7 +639,6 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo) isVideoMuted, isRecording, accInfo.profileInfo.type == lrc::api::profile::Type::SIP, - !convInfo.confId.isEmpty(), bestName); } diff --git a/src/calladapter.h b/src/calladapter.h index bdd7015d798f679d730d93f4946d0f350dcf841c..06d9b84aa93e4d81ca36be5334b5c9caab734358 100644 --- a/src/calladapter.h +++ b/src/calladapter.h @@ -95,7 +95,6 @@ Q_SIGNALS: bool isVideoMuted, bool isRecording, bool isSIP, - bool isConferenceCall, const QString& bestName); void remoteRecordingChanged(const QStringList& peers, bool state); void eraseRemoteRecording(); diff --git a/src/calloverlaymodel.cpp b/src/calloverlaymodel.cpp index fb130de346dd4016665c1a2a1fd8b029e237f95b..beb49d0198d186397c92b7c50e5c5ef8ebe46c6f 100644 --- a/src/calloverlaymodel.cpp +++ b/src/calloverlaymodel.cpp @@ -44,15 +44,10 @@ CallControlListModel::data(const QModelIndex& index, int role) const auto item = data_.at(index.row()); switch (role) { - case Role::DummyRole: - break; -#define X(t, role) \ - case Role::role: \ - return QVariant::fromValue(item.role); - CC_ROLES -#undef X - default: - break; + case Role::ItemAction: + return QVariant::fromValue(item.itemAction); + case Role::BadgeCount: + return QVariant::fromValue(item.badgeCount); } return QVariant(); } @@ -62,9 +57,8 @@ CallControlListModel::roleNames() const { using namespace CallControl; QHash<int, QByteArray> roles; -#define X(t, role) roles[role] = #role; - CC_ROLES -#undef X + roles[ItemAction] = "ItemAction"; + roles[BadgeCount] = "BadgeCount"; return roles; } @@ -73,7 +67,7 @@ CallControlListModel::setBadgeCount(int row, int count) { if (row >= rowCount()) return; - data_[row].BadgeCount = count; + data_[row].badgeCount = count; auto idx = index(row, 0); Q_EMIT dataChanged(idx, idx); } @@ -86,6 +80,12 @@ CallControlListModel::addItem(const CallControl::Item& item) endResetModel(); } +void +CallControlListModel::clearData() +{ + data_.clear(); +} + IndexRangeFilterProxyModel::IndexRangeFilterProxyModel(QAbstractListModel* parent) : QSortFilterProxyModel(parent) { @@ -129,25 +129,15 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent) } void -CallOverlayModel::addPrimaryControl(const QVariantMap& props) +CallOverlayModel::addPrimaryControl(const QVariant& action) { - CallControl::Item item { -#define X(t, role) props[#role].value<t>(), - CC_ROLES -#undef X - }; - primaryModel_->addItem(item); + primaryModel_->addItem(CallControl::Item {action.value<QObject*>()}); } void -CallOverlayModel::addSecondaryControl(const QVariantMap& props) -{ - CallControl::Item item { -#define X(t, role) props[#role].value<t>(), - CC_ROLES -#undef X - }; - secondaryModel_->addItem(item); +CallOverlayModel::addSecondaryControl(const QVariant& action) +{ + secondaryModel_->addItem(CallControl::Item {action.value<QObject*>()}); setControlRanges(); } @@ -187,6 +177,13 @@ CallOverlayModel::overflowHiddenModel() return QVariant::fromValue(overflowHiddenModel_); } +void +CallOverlayModel::clearControls() +{ + primaryModel_->clearData(); + secondaryModel_->clearData(); +} + void CallOverlayModel::registerFilter(QQuickWindow* object, QQuickItem* item) { diff --git a/src/calloverlaymodel.h b/src/calloverlaymodel.h index 856a71a3dd91d78aaf0d97b668aff37ca199f36c..1776184a9587a0a7eb6c1e32639d0cf05f7b0646 100644 --- a/src/calloverlaymodel.h +++ b/src/calloverlaymodel.h @@ -27,28 +27,15 @@ #include <QSortFilterProxyModel> #include <QQuickItem> -#define CC_ROLES \ - X(QObject*, ItemAction) \ - X(int, BadgeCount) \ - X(bool, HasBackground) \ - X(QObject*, MenuAction) \ - X(QString, Name) - namespace CallControl { Q_NAMESPACE -enum Role { - DummyRole = Qt::UserRole + 1, -#define X(t, role) role, - CC_ROLES -#undef X -}; +enum Role { ItemAction = Qt::UserRole + 1, BadgeCount }; Q_ENUM_NS(Role) struct Item { -#define X(t, role) t role; - CC_ROLES -#undef X + QObject* itemAction; + int badgeCount {0}; }; } // namespace CallControl @@ -64,6 +51,7 @@ public: void setBadgeCount(int row, int count); void addItem(const CallControl::Item& item); + void clearData(); private: QList<CallControl::Item> data_; @@ -93,9 +81,10 @@ class CallOverlayModel : public QObject public: CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr); - Q_INVOKABLE void addPrimaryControl(const QVariantMap& props); - Q_INVOKABLE void addSecondaryControl(const QVariantMap& props); + Q_INVOKABLE void addPrimaryControl(const QVariant& action); + Q_INVOKABLE void addSecondaryControl(const QVariant& action); Q_INVOKABLE void setBadgeCount(int row, int count); + Q_INVOKABLE void clearControls(); Q_INVOKABLE QVariant primaryModel(); Q_INVOKABLE QVariant secondaryModel(); diff --git a/src/commoncomponents/HalfPill.qml b/src/commoncomponents/HalfPill.qml new file mode 100644 index 0000000000000000000000000000000000000000..6d9564eeb79c10d41533178c7777ffc1c4aac7c2 --- /dev/null +++ b/src/commoncomponents/HalfPill.qml @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick 2.14 + +Item { + id: root + + enum Type { + None, + Top, + Left, + Bottom, + Right + } + + property int type: HalfPill.None + property int radius: 0 + property alias color: rect.color + + clip: true + + Rectangle { + id: rect + + property bool horizontal: type === HalfPill.Left || + type == HalfPill.Right + property bool direction: type === HalfPill.Right || + type == HalfPill.Bottom + + radius: root.radius * (type !== HalfPill.None) + width: root.size + radius + height: root.size + radius + anchors.fill: root + anchors.leftMargin: horizontal * direction * -radius + anchors.rightMargin: horizontal * !direction * -radius + anchors.topMargin: !horizontal * direction * -radius + anchors.bottomMargin: !horizontal * !direction * -radius + } +} diff --git a/src/commoncomponents/MaterialToolTip.qml b/src/commoncomponents/MaterialToolTip.qml new file mode 100644 index 0000000000000000000000000000000000000000..f7e1a41059afe74f3670312169406ba59cc4f29e --- /dev/null +++ b/src/commoncomponents/MaterialToolTip.qml @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick 2.0 +import QtQuick.Controls 2.14 + +import net.jami.Constants 1.0 + +ToolTip { + id: root + + onVisibleChanged: { + if (visible) + animation.start() + } + + contentItem: Text { + id: label + text: root.text + font: root.font + color: "white" + } + + background: Rectangle { + color: "#c4272727" + radius: 5 + } + + ParallelAnimation { + id: animation + NumberAnimation { + target: root; properties: "opacity" + from: 0; to: 1.0 + duration: JamiTheme.shortFadeDuration + } + NumberAnimation { + target: root; properties: "scale" + from: 0.5; to: 1.0 + duration: JamiTheme.shortFadeDuration * 0.5 + } + } +} diff --git a/src/commoncomponents/ResponsiveImage.qml b/src/commoncomponents/ResponsiveImage.qml index a727c627d49c1128931a63cdded4b76dfc1c667c..a703d23a99a375b5b287cfb498e76dd97a9f283e 100644 --- a/src/commoncomponents/ResponsiveImage.qml +++ b/src/commoncomponents/ResponsiveImage.qml @@ -25,8 +25,8 @@ import net.jami.Models 1.0 Image { id: root - property real containerWidth - property real containerHeight + property real containerWidth: 30 + property real containerHeight: 30 property int padding: 0 property point offset: Qt.point(0, 0) diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index aa61f241b2207bdcb180816ca5f8e57f9a8bc5bb..208c73b7c7bc5657f4e07e27b7150d9bdfa1e4eb 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -186,7 +186,7 @@ Item { property string peerStoppedRecording: qsTr("Peer stopped recording") property string isCallingYou: qsTr("is calling you") - // CallOverlayButtonGroup + // CallOverlay property string mute: qsTr("Mute") property string unmute: qsTr("Unmute") property string hangup: qsTr("End call") diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml index f70caac3eea636a2c25b84493a514f7695c30916..3cb860b186443d3102e0150f567e7139ac4efeee 100644 --- a/src/constant/JamiTheme.qml +++ b/src/constant/JamiTheme.qml @@ -164,8 +164,9 @@ Item { property color bgDarkMode_: rgba256(32, 32, 32, 100) property int shortFadeDuration: 150 - property int overlayFadeDelay: 2000 - property int overlayFadeDuration: 500 + property int recordBlinkDuration: 500 + property int overlayFadeDelay: 4000 + property int overlayFadeDuration: 250 property int smartListTransitionDuration: 120 // Sizes diff --git a/src/mainview/components/CallActionBar.qml b/src/mainview/components/CallActionBar.qml new file mode 100644 index 0000000000000000000000000000000000000000..67cdb0613cf9cdfb5099088baad0a5ec64e8baa4 --- /dev/null +++ b/src/mainview/components/CallActionBar.qml @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2021 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 + +import net.jami.Models 1.0 +import net.jami.Adapters 1.0 +import net.jami.Constants 1.0 + +import "../../commoncomponents" + +Control { + id: root + + property alias overflowOpen: overflowButton.popup.visible + property bool subMenuOpen: false + + property real itemSpacing: 2 + property bool localIsRecording: false + + signal chatClicked + signal addToConferenceClicked + signal transferClicked // TODO: bind this + signal shareScreenClicked + signal shareScreenAreaClicked // TODO: bind this + signal pluginsClicked + + Component { + id: buttonDelegate + + CallButtonDelegate { + width: root.height + height: width + onSubMenuVisibleChanged: subMenuOpen = subMenuVisible + } + } + + Connections { + target: AvAdapter + + // TODO: audio device list updates + function onVideoDeviceListChanged(listIsEmpty) { + videoInputDeviceListModel.reset(); + } + } + + property list<Action> menuActions: [ + Action { + id: audioInputMenuAction + text: JamiStrings.selectAudioInputDevice + property var listModel: AudioDeviceModel { + id: audioInputDeviceListModel + lrcInstance: LRCInstance + type: AudioDeviceModel.Type.Record + } + function accept(index) { + AvAdapter.stopAudioMeter(false) + AVModel.setInputDevice(listModel.data( + listModel.index(index, 0), + AudioDeviceModel.RawDeviceName)) + AvAdapter.startAudioMeter(false) + } + }, + Action { + id: audioOutputMenuAction + text: JamiStrings.selectAudioOutputDevice + property var listModel: AudioDeviceModel { + id: audioOutputDeviceListModel + lrcInstance: LRCInstance + type: AudioDeviceModel.Type.Playback + } + function accept(index) { + AvAdapter.stopAudioMeter(false) + AVModel.setOutputDevice(listModel.data( + listModel.index(index, 0), + AudioDeviceModel.RawDeviceName)) + AvAdapter.startAudioMeter(false) + } + }, + Action { + id: videoInputMenuAction + text: JamiStrings.selectVideoDevice + property var listModel: VideoInputDeviceModel { + id: videoInputDeviceListModel + lrcInstance: LRCInstance + } + function accept(index) { + if(listModel.deviceCount() < 1) + return + try { + var deviceId = listModel.data( + listModel.index(index, 0), + VideoInputDeviceModel.DeviceId) + var deviceName = listModel.data( + listModel.index(index, 0), + VideoInputDeviceModel.DeviceName) + if(deviceId.length === 0) { + console.warn("Couldn't find device: " + deviceName) + return + } + if (AVModel.getCurrentVideoCaptureDevice() !== deviceId) { + AVModel.setCurrentVideoCaptureDevice(deviceId) + AVModel.setDefaultDevice(deviceId) + } + AvAdapter.selectVideoInputDeviceById(deviceId) + } catch(err){ console.warn(err.message) } + } + } + ] + + property list<Action> primaryActions: [ + Action { + id: muteAudioAction + onTriggered: CallAdapter.muteThisCallToggle() + checkable: true + icon.source: checked ? + "qrc:/images/icons/mic_off-24px.svg" : + "qrc:/images/icons/mic-24px.svg" + icon.color: checked ? "red" : "white" + text: !checked ? JamiStrings.mute : JamiStrings.unmute + property var menuAction: audioInputMenuAction + }, + Action { + id: hangupAction + onTriggered: CallAdapter.hangUpThisCall() + icon.source: "qrc:/images/icons/ic_call_end_white_24px.svg" + icon.color: "white" + text: JamiStrings.hangup + property bool hasBg: true + }, + Action { + id: muteVideoAction + onTriggered: CallAdapter.videoPauseThisCallToggle() + checkable: true + icon.source: checked ? + "qrc:/images/icons/videocam_off-24px.svg" : + "qrc:/images/icons/videocam-24px.svg" + icon.color: checked ? "red" : "white" + text: !checked ? JamiStrings.pauseVideo : JamiStrings.resumeVideo + property var menuAction: videoInputMenuAction + } + ] + + property list<Action> secondaryActions: [ + Action { + id: audioOutputAction + // temp hack for missing back-end, just open device selection + property bool bypassMuteAction: true + checkable: !bypassMuteAction + icon.source: "qrc:/images/icons/spk_black_24dp.svg" + icon.color: "white" + text: JamiStrings.selectAudioOutputDevice + property var menuAction: audioOutputMenuAction + }, + Action { + id: addPersonAction + onTriggered: root.addToConferenceClicked() + icon.source: "qrc:/images/icons/add_people_black_24dp.svg" + icon.color: "white" + text: JamiStrings.addParticipants + }, + Action { + id: chatAction + onTriggered: root.chatClicked() + icon.source: "qrc:/images/icons/chat_black_24dp.svg" + icon.color: "white" + text: JamiStrings.chat + }, + Action { + id: shareAction + onTriggered: root.shareScreenClicked() + icon.source: "qrc:/images/icons/share_screen_black_24dp.svg" + icon.color: "white" + text: JamiStrings.shareScreen + property real size: 34 + }, + Action { + id: recordAction + onTriggered: CallAdapter.recordThisCallToggle() + checkable: true + icon.source: "qrc:/images/icons/record_black_24dp.svg" + icon.color: checked ? "red" : "white" + text: !checked ? JamiStrings.startRec : JamiStrings.stopRec + property bool blinksWhenChecked: true + property real size: 28 + }, + Action { + id: pluginsAction + onTriggered: root.pluginsClicked() + icon.source: "qrc:/images/icons/plugins-24px.svg" + icon.color: "white" + text: JamiStrings.viewPlugin + enabled: UtilsAdapter.checkShowPluginsButton(true) + } + ] + + property var overflowItemCount + + Connections { + target: callOverlay + function onIsAudioOnlyChanged() { reset() } + function onIsSIPChanged() { reset() } + function onIsModeratorChanged() { reset() } + } + + function reset() { + CallOverlayModel.clearControls(); + + // centered controls + CallOverlayModel.addPrimaryControl(muteAudioAction) + CallOverlayModel.addPrimaryControl(hangupAction) + if (!isAudioOnly) + CallOverlayModel.addPrimaryControl(muteVideoAction) + + // overflow controls + CallOverlayModel.addSecondaryControl(audioOutputAction) + if (isModerator && !isSIP) + CallOverlayModel.addSecondaryControl(addPersonAction) + CallOverlayModel.addSecondaryControl(chatAction) + if (!isAudioOnly) + CallOverlayModel.addSecondaryControl(shareAction) + CallOverlayModel.addSecondaryControl(recordAction) + if (UtilsAdapter.checkShowPluginsButton(true)) + CallOverlayModel.addSecondaryControl(pluginsAction) + overflowItemCount = CallOverlayModel.secondaryModel().rowCount() + + muteAudioAction.checked = isAudioMuted + muteVideoAction.checked = isVideoMuted + } + + Item { + id: centralControls + anchors.centerIn: parent + width: childrenRect.width + height: root.height + + RowLayout { + spacing: 0 + + ListView { + property bool centeredGroup: true + + orientation: ListView.Horizontal + implicitWidth: contentWidth + implicitHeight: contentHeight + + model: CallOverlayModel.primaryModel() + delegate: buttonDelegate + } + } + } + Item { + id: overflowRect + property real remainingSpace: (root.width - centralControls.width) / 2 + anchors.right: parent.right + width: childrenRect.width + height: root.height + + RowLayout { + spacing: itemSpacing + + ListView { + id: overflowItemListView + + orientation: ListView.Horizontal + implicitWidth: contentWidth + implicitHeight: overflowRect.height + + spacing: itemSpacing + + property int overflowIndex: { + var maxItems = Math.floor((overflowRect.remainingSpace - 24) / root.height) - 1 + return Math.min(overflowItemCount, maxItems) + } + property int nOverflowItems: overflowItemCount - overflowIndex + onNOverflowItemsChanged: { + var diff = overflowItemListView.count - nOverflowItems + var effectiveOverflowIndex = overflowIndex + if (effectiveOverflowIndex === overflowItemCount - 1) + effectiveOverflowIndex += diff + + CallOverlayModel.overflowIndex = effectiveOverflowIndex + } + + model: CallOverlayModel.overflowModel() + delegate: buttonDelegate + } + ComboBox { + id: overflowButton + + visible: CallOverlayModel.overflowIndex < overflowItemCount + width: root.height + height: width + + model: CallOverlayModel.overflowHiddenModel() + + delegate: buttonDelegate + + indicator: null + + contentItem: Text { + text: "⋮" + color: "white" + font.pointSize: 24 + font.weight: Font.Bold + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + } + + background: Rectangle { + implicitWidth: root.height + implicitHeight: implicitWidth + color: overflowButton.down ? + "#80aaaaaa" : + overflowButton.hovered ? + "#80777777" : + "#80444444" + } + + Item { + implicitHeight: children[0].contentHeight + width: overflowButton.width + anchors.bottom: parent.top + anchors.bottomMargin: itemSpacing + visible: !overflowButton.popup.visible + ListView { + spacing: itemSpacing + anchors.fill: parent + model: CallOverlayModel.overflowVisibleModel() + delegate: buttonDelegate + ScrollIndicator.vertical: ScrollIndicator {} + + add: Transition { + NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 80 } + NumberAnimation { property: "scale"; from: 0; to: 1.0; duration: 80 } + } + } + } + + popup: Popup { + y: overflowButton.height + itemSpacing + width: overflowButton.width + implicitHeight: contentItem.implicitHeight + padding: 0 + + contentItem: ListView { + id: overflowListView + spacing: itemSpacing + implicitHeight: contentHeight + model: overflowButton.popup.visible ? + overflowButton.delegateModel : + null + + ScrollIndicator.vertical: ScrollIndicator {} + + } + + background: Rectangle { + color: "transparent" + } + } + } + } + } +} diff --git a/src/mainview/components/CallButtonDelegate.qml b/src/mainview/components/CallButtonDelegate.qml new file mode 100644 index 0000000000000000000000000000000000000000..86e0f8e6933f5478151df96939f24ab90080d692 --- /dev/null +++ b/src/mainview/components/CallButtonDelegate.qml @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2021 by Savoir-faire Linux + * Author: Andreas Traczyk <andreas.traczyk@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 <http://www.gnu.org/licenses/>. + */ + +import QtQuick 2.14 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.14 + +import net.jami.Models 1.0 +import net.jami.Constants 1.0 + +import "../../commoncomponents" + +ItemDelegate { + id: wrapper + + property bool isFirst: index < 1 + property bool isLast: index + 1 < ListView.view.count ? false : true + property bool hasLast: ListView.view.centeredGroup !== undefined + property bool isVertical: wrapper.ListView.view.orientation === ListView.Vertical + + property alias subMenuVisible: menu.popup.visible + + action: ItemAction + checkable: ItemAction.checkable + + // hide the action's visual elements like the blurry looking icon + icon.source: "" + text: "" + + z: index + + // TODO: remove this when output volume control is implemented + MouseArea { + visible: ItemAction.bypassMuteAction !== undefined && + ItemAction.bypassMuteAction && !menu.popup.visible + anchors.fill: wrapper + onClicked: menu.popup.open() + } + + background: HalfPill { + anchors.fill: parent + radius: 5 + color: { + if (supplimentaryBackground.visible) + return "#c4272727" + return wrapper.down ? + "#c4777777" : + wrapper.hovered && !menu.hovered ? + "#c4444444" : + "#c4272727" + } + type: { + if (isVertical) { + if (isFirst) + return HalfPill.Top + else if (isLast && hasLast) + return HalfPill.Bottom + } else { + if (isFirst) + return HalfPill.Left + else if (isLast && hasLast) + return HalfPill.Right + } + return HalfPill.None + } + + Behavior on color { + ColorAnimation { duration: JamiTheme.shortFadeDuration } + } + } + + Rectangle { + id: supplimentaryBackground + + visible: ItemAction.hasBg !== undefined + color: wrapper.down ? + Qt.lighter(JamiTheme.refuseRed, 1.5) : + wrapper.hovered && !menu.hovered ? + JamiTheme.refuseRed : + JamiTheme.refuseRedTransparent + anchors.fill: parent + radius: width / 2 + + Behavior on color { + ColorAnimation { duration: JamiTheme.shortFadeDuration } + } + } + + ResponsiveImage { + id: icon + + // TODO: remove this when the icons are size corrected + property real size: ItemAction.size !== undefined ? + ItemAction.size : 30 + containerWidth: size + containerHeight: size + + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + source: ItemAction.icon.source + color: ItemAction.icon.color + + SequentialAnimation on opacity { + loops: Animation.Infinite + running: ItemAction.blinksWhenChecked !== undefined && + ItemAction.blinksWhenChecked && checked + NumberAnimation { from: 1; to: 0; duration: JamiTheme.recordBlinkDuration } + NumberAnimation { from: 0; to: 1; duration: JamiTheme.recordBlinkDuration } + } + } + + // custom anchor for the tooltips + Item { + anchors.top: !isVertical ? parent.bottom : undefined + anchors.topMargin: 25 + anchors.horizontalCenter: !isVertical ? parent.horizontalCenter : undefined + + anchors.right: isVertical ? parent.left : undefined + anchors.rightMargin: isVertical ? toolTip.contentWidth / 2 + 12 : 0 + anchors.verticalCenter: isVertical ? parent.verticalCenter : undefined + anchors.verticalCenterOffset: isVertical ? toolTip.contentHeight / 2 + 4 : 0 + + MaterialToolTip { + id: toolTip + parent: parent + visible: text.length > 0 && (wrapper.hovered || menu.hovered) + text: menu.hovered ? menuAction.text : ItemAction.text + verticalPadding: 1 + font.pointSize: 9 + } + } + + property var menuAction: ItemAction.menuAction + + ComboBox { + id: menu + + indicator: null + + visible: menuAction !== undefined && !BadgeCount + anchors.horizontalCenter: parent.horizontalCenter + width: 18 + height: width + y: -4 + + Connections { + target: menuAction !== undefined ? + menuAction : + null + function onTriggered() { + itemListView.currentIndex = + menuAction.listModel.getCurrentIndex() + } + } + + contentItem: Text { + text: "^" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: "white" + } + + background: Rectangle { + color: menu.down ? + "#aaaaaa" : + menu.hovered ? + "#777777" : + "#444444" + radius: 4 + } + + onActivated: menuAction.accept(index) + model: visible ? menuAction.listModel : null + delegate: ItemDelegate { + id: menuItem + + width: itemListView.menuItemWidth + height: itemListView.menuItemHeight + background: Rectangle { + anchors.fill: parent + color: menuItem.down ? + "#c4aaaaaa" : + menuItem.hovered ? + "#c4777777" : + "transparent" + } + contentItem: RowLayout { + anchors.fill: parent + anchors.margins: 6 + ResponsiveImage { + source: menuItem.ListView.isCurrentItem ? + "qrc:/images/icons/check_box-24px.svg" : + "qrc:/images/icons/check_box_outline_blank-24px.svg" + layer.enabled: true + layer.effect: ColorOverlay { color: "white" } + } + Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + text: DeviceName + elide: Text.ElideRight + color: "white" + } + } + } + + popup: Popup { + id: itemPopup + + y: isVertical ? + -(implicitHeight - wrapper.height) / 2 : + -implicitHeight - 12 + x: isVertical ? + -implicitWidth - 24 : + -(implicitWidth - wrapper.width) / 2 - 18 + + implicitWidth: contentItem.implicitWidth + implicitHeight: contentItem.implicitHeight + leftPadding: 0 + rightPadding: 0 + + onOpened: menuAction.triggered() + + contentItem: ListView { + id: itemListView + + property real menuItemWidth: 0 + property real menuItemHeight: 39 + + orientation: ListView.Vertical + implicitWidth: menuItemWidth + implicitHeight: Math.min(contentHeight, + menuItemHeight * 6) + 24 + + ScrollIndicator.vertical: ScrollIndicator {} + + clip: true + + model: menu.delegateModel + + TextMetrics { id: itemTextMetrics } + + // recalc list width based on max item width + onCountChanged: { + // Hack: use AudioDeviceModel.DeviceName role for video as well + var maxWidth = 0 + for (var i = 0; i < count; ++i) { + var idx = menuAction.listModel.index(i, 0) + itemTextMetrics.text = menuAction.listModel.data( + idx, AudioDeviceModel.DeviceName) + if (itemTextMetrics.boundingRect.width > maxWidth) + maxWidth = itemTextMetrics.boundingRect.width + } + // 30(icon) + 5(layout spacing) + 12(margins) + menuItemWidth = Math.min(256, maxWidth + 30 + 5 + 12) + } + } + + background: Rectangle { + anchors.fill: parent + radius: 5 + color: "#c4272727" + } + } + + layer.enabled: true + layer.effect: DropShadow { + z: -1 + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + samples: 16 + color: "#80000000" + } + } + + BadgeNotifier { + id: badge + + count: BadgeCount + anchors.horizontalCenter: parent.horizontalCenter + width: 18 + height: width + radius: 4 + y: -4 + } +} diff --git a/src/mainview/components/CallOverlay.qml b/src/mainview/components/CallOverlay.qml index ea5b150fc8b492052de4e77ec5e28268e945294a..8e99e8e9027bac96d2ed4316853ae1d431999e94 100644 --- a/src/mainview/components/CallOverlay.qml +++ b/src/mainview/components/CallOverlay.qml @@ -2,7 +2,7 @@ * Copyright (C) 2020 by Savoir-faire Linux * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@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 @@ -29,6 +29,8 @@ import net.jami.Adapters 1.0 import net.jami.Constants 1.0 import "../js/contactpickercreation.js" as ContactPickerCreation +import "../js/selectscreenwindowcreation.js" as SelectScreenWindowCreation +import "../js/screenrubberbandcreation.js" as ScreenRubberBandCreation import "../js/pluginhandlerpickercreation.js" as PluginHandlerPickerCreation import "../../commoncomponents" @@ -37,13 +39,18 @@ Item { id: root property alias participantsLayer: __participantsLayer - property string timeText: "00:00" - property string remoteRecordingLabel: "" - property bool isVideoMuted: true - property bool isAudioOnly: false + + property bool isPaused + property bool isAudioOnly + property bool isAudioMuted + property bool isVideoMuted + property bool isRecording + property bool isSIP + property bool isModerator + property string bestName: "" - signal overlayChatButtonClicked + signal chatButtonClicked onVisibleChanged: if (!visible) callViewContextMenu.close() @@ -54,25 +61,23 @@ Item { function setRecording(localIsRecording) { callViewContextMenu.localIsRecording = localIsRecording - recordingRect.visible = localIsRecording + mainOverlay.recordingVisible = localIsRecording || callViewContextMenu.peerIsRecording } - function updateButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, - isRecording, isSIP, isConferenceCall) { - root.isVideoMuted = isVideoMuted - callViewContextMenu.isSIP = isSIP - callViewContextMenu.isPaused = isPaused - callViewContextMenu.isAudioOnly = isAudioOnly - callViewContextMenu.localIsRecording = isRecording - recordingRect.visible = isRecording - callOverlayButtonGroup.setButtonStatus(isPaused, isAudioOnly, - isAudioMuted, isVideoMuted, - isSIP, isConferenceCall) - } + function updateUI(isPaused, isAudioOnly, isAudioMuted, + isVideoMuted, isRecording, isSIP) { + if (isPaused !== undefined) { + root.isPaused = isPaused + root.isAudioOnly = isAudioOnly + root.isAudioMuted = isAudioMuted + root.isVideoMuted = isVideoMuted + root.isRecording = isRecording + root.isSIP = isSIP + mainOverlay.recordingVisible = isRecording + } - function updateMenu() { - callOverlayButtonGroup.updateMenu() + root.isModerator = CallAdapter.isCurrentModerator() } function showOnHoldImage(visible) { @@ -109,17 +114,19 @@ Item { : JamiStrings.isRecording) } - remoteRecordingLabel = state? label : JamiStrings.peerStoppedRecording + mainOverlay.remoteRecordingLabel = state ? + label : + JamiStrings.peerStoppedRecording callViewContextMenu.peerIsRecording = state - recordingRect.visible = callViewContextMenu.localIsRecording + mainOverlay.recordingVisible = callViewContextMenu.localIsRecording || callViewContextMenu.peerIsRecording callOverlayRectMouseArea.entered() } function resetRemoteRecording() { - remoteRecordingLabel = "" + mainOverlay.remoteRecordingLabel = "" callViewContextMenu.peerIsRecording = false - recordingRect.visible = callViewContextMenu.localIsRecording + mainOverlay.recordingVisible = callViewContextMenu.localIsRecording } SipInputPanel { @@ -143,182 +150,61 @@ Item { source: "qrc:/images/icons/ic_pause_white_100px.svg" } - Item { - id: mainOverlay - - anchors.fill: parent - opacity: 0 - - // (un)subscribe to an app-wide mouse move event trap filtered - // for the overlay's geometry - onVisibleChanged: visible ? - CallOverlayModel.registerFilter(appWindow, this) : - CallOverlayModel.unregisterFilter(appWindow, this) - - Connections { - target: CallOverlayModel - - function onMouseMoved(item) { - if (item === mainOverlay) { - mainOverlay.opacity = 1 - fadeOutTimer.restart() - } - } - } - - // control overlay fade out. - Timer { - id: fadeOutTimer - interval: JamiTheme.overlayFadeDelay - onTriggered: { - if (callOverlayButtonGroup.hovered) - return - mainOverlay.opacity = 0 - resetLabelsTimer.restart() - } - } + function openContactPicker(type) { + ContactPickerCreation.createContactPickerObjects(type, root) + ContactPickerCreation.openContactPicker() + } - // Timer to reset recording label and call duration time - Timer { - id: resetLabelsTimer - interval: 1000 - running: root.visible - repeat: true - onTriggered: { - timeText = CallAdapter.getCallDurationTime(LRCInstance.currentAccountId, - LRCInstance.selectedConvUid) - if (mainOverlay.opacity === 0 && !callViewContextMenu.peerIsRecording) - remoteRecordingLabel = "" - } + function openShareScreen() { + if (Qt.application.screens.length === 1) { + AvAdapter.shareEntireScreen(0) + } else { + SelectScreenWindowCreation.createSelectScreenWindowObject() + SelectScreenWindowCreation.showSelectScreenWindow() } + } - Item { - id: overlayUpperPartRect - - anchors.top: parent.top - - width: parent.width - height: 50 - - RowLayout { - anchors.fill: parent - - Text { - id: jamiBestNameText - - Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft - Layout.preferredWidth: overlayUpperPartRect.width / 3 - Layout.preferredHeight: 50 - leftPadding: 16 - - font.pointSize: JamiTheme.textFontSize - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - - text: textMetricsjamiBestNameText.elidedText - color: "white" - - TextMetrics { - id: textMetricsjamiBestNameText - font: jamiBestNameText.font - text: { - if (!root.isAudioOnly) { - if (remoteRecordingLabel === "") { - return root.bestName - } else { - return remoteRecordingLabel - } - } - return "" - } - elideWidth: overlayUpperPartRect.width / 3 - elide: Qt.ElideRight - } - } - - Text { - id: callTimerText - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.preferredWidth: 64 - Layout.minimumWidth: 64 - Layout.preferredHeight: 48 - Layout.rightMargin: recordingRect.visible? - 0 : JamiTheme.preferredMarginSize - font.pointSize: JamiTheme.textFontSize - horizontalAlignment: Text.AlignRight - verticalAlignment: Text.AlignVCenter - text: textMetricscallTimerText.elidedText - color: "white" - TextMetrics { - id: textMetricscallTimerText - font: callTimerText.font - text: timeText - elideWidth: overlayUpperPartRect.width / 4 - elide: Qt.ElideRight - } - } - - Rectangle { - id: recordingRect - Layout.alignment: Qt.AlignVCenter | Qt.AlignRight - Layout.rightMargin: JamiTheme.preferredMarginSize - height: 16 - width: 16 - radius: height / 2 - color: "red" - - SequentialAnimation on color { - loops: Animation.Infinite - running: true - ColorAnimation { from: "red"; to: "transparent"; duration: 500 } - ColorAnimation { from: "transparent"; to: "red"; duration: 500 } - } - } - } + function openShareScreenArea() { + if (Qt.platform.os !== "windows") { + AvAdapter.shareScreenArea(0, 0, 0, 0) + } else { + ScreenRubberBandCreation.createScreenRubberBandWindowObject() + ScreenRubberBandCreation.showScreenRubberBandWindow() } + } - CallOverlayButtonGroup { - id: callOverlayButtonGroup + function openPluginsMenu() { + PluginHandlerPickerCreation.createPluginHandlerPickerObjects(root, true) + PluginHandlerPickerCreation.openPluginHandlerPicker() + } - anchors.bottom: parent.bottom - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter + MainOverlay { + id: mainOverlay - height: 56 - width: root.width + anchors.fill: parent - onChatButtonClicked: { - root.overlayChatButtonClicked() - } + bestName: root.bestName - onAddToConferenceButtonClicked: { - // Create contact picker - conference. - ContactPickerCreation.createContactPickerObjects( - ContactList.CONFERENCE, - root) - ContactPickerCreation.openContactPicker() - } + Connections { + target: mainOverlay.callActionBar + function onChatClicked() { root.chatButtonClicked() } + function onAddToConferenceClicked() { openContactPicker(ContactList.CONFERENCE) } + function onTransferClicked() { openContactPicker(ContactList.TRANSFER) } + function onShareScreenClicked() { openShareScreen() } + function onShareScreenAreaClicked() { openShareScreenArea() } + function onPluginsClicked() { openPluginsMenu() } } - - Behavior on opacity { NumberAnimation { duration: JamiTheme.overlayFadeDuration }} } CallViewContextMenu { id: callViewContextMenu - onTransferCallButtonClicked: { - // Create contact picker - sip transfer. - ContactPickerCreation.createContactPickerObjects( - ContactList.TRANSFER, - root) - ContactPickerCreation.openContactPicker() - } + isSIP: root.isSIP + isPaused: root.isPaused + isAudioOnly: root.isAudioOnly + localIsRecording: root.isRecording - onPluginItemClicked: { - // Create plugin handler picker - PLUGINS - PluginHandlerPickerCreation.createPluginHandlerPickerObjects(root, true) - PluginHandlerPickerCreation.openPluginHandlerPicker() - } + onTransferCallButtonClicked: openContactPicker(ContactList.TRANSFER) + onPluginItemClicked: openPluginsMenu() } } diff --git a/src/mainview/components/CallOverlayButtonGroup.qml b/src/mainview/components/CallOverlayButtonGroup.qml deleted file mode 100644 index 4783fb1d40aab295478cdb769fd87f8c68838204..0000000000000000000000000000000000000000 --- a/src/mainview/components/CallOverlayButtonGroup.qml +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2020 by Savoir-faire Linux - * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> - * 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.14 -import QtQml 2.14 - -import net.jami.Adapters 1.0 -import net.jami.Models 1.0 -import net.jami.Constants 1.0 - -import "../../commoncomponents" - -Control { - id: root - - // ButtonCounts here is to make sure that flow layout margin is calculated correctly, - // since no other methods can make buttons at the layout center. - property var isModerator: true - property var isSip: false - - signal chatButtonClicked - signal addToConferenceButtonClicked - - function updateMenu() { - root.isModerator = CallAdapter.isCurrentModerator() - addToConferenceButton.visible = !root.isSip && root.isModerator - } - - function setButtonStatus(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, - isSIP, isConferenceCall) { - root.isModerator = CallAdapter.isCurrentModerator() - root.isSip = isSIP - noVideoButton.visible = !isAudioOnly - addToConferenceButton.visible = !root.isSIP && root.isModerator - - noMicButton.checked = isAudioMuted - noVideoButton.checked = isVideoMuted - } - - z: 2 - - RowLayout { - id: callOverlayButtonGroup - - spacing: 8 - height: 56 - - anchors.fill: parent - - Item { - Layout.preferredWidth: { - // TODO: refactor with Flow if possible - // 6 is the number of button - // If ~ 500px, go into wide mode - if (callOverlayButtonGroup.width < (JamiTheme.callButtonPreferredSize * 6 - - callOverlayButtonGroup.spacing * 6 + 300)) { - return 0 - } else { - return callOverlayButtonGroup.width / 2 - JamiTheme.callButtonPreferredSize * 1.5 - - callOverlayButtonGroup.spacing - } - } - } - - PushButton { - id: noMicButton - - Layout.leftMargin: 8 - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - - pressedColor: JamiTheme.invertedPressedButtonColor - hoveredColor: JamiTheme.invertedHoveredButtonColor - normalColor: JamiTheme.invertedNormalButtonColor - - normalImageSource: "qrc:/images/icons/mic-24px.svg" - imageColor: JamiTheme.whiteColor - checkable: true - checkedImageSource: "qrc:/images/icons/mic_off-24px.svg" - checkedImageColor: JamiTheme.declineButtonPressedRed - - toolTipText: !checked ? JamiStrings.mute : JamiStrings.unmute - - onClicked: CallAdapter.muteThisCallToggle() - } - - PushButton { - id: hangUpButton - - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - - pressedColor: JamiTheme.declineButtonPressedRed - hoveredColor: JamiTheme.declineButtonHoverRed - normalColor: JamiTheme.declineButtonRed - - source: "qrc:/images/icons/ic_call_end_white_24px.svg" - imageColor: JamiTheme.whiteColor - - toolTipText: JamiStrings.hangup - - onClicked: CallAdapter.hangUpThisCall() - } - - PushButton { - id: noVideoButton - - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - - pressedColor: JamiTheme.invertedPressedButtonColor - hoveredColor: JamiTheme.invertedHoveredButtonColor - normalColor: JamiTheme.invertedNormalButtonColor - - normalImageSource: "qrc:/images/icons/videocam-24px.svg" - imageColor: JamiTheme.whiteColor - checkable: true - checkedImageSource: "qrc:/images/icons/videocam_off-24px.svg" - checkedImageColor: JamiTheme.declineButtonPressedRed - - toolTipText: !checked ? JamiStrings.pauseVideo : JamiStrings.resumeVideo - - onClicked: CallAdapter.videoPauseThisCallToggle() - } - - Item { - Layout.fillWidth: true - } - - PushButton { - id: addToConferenceButton - - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - visible: !isModerator && !isSip - - pressedColor: JamiTheme.invertedPressedButtonColor - hoveredColor: JamiTheme.invertedHoveredButtonColor - normalColor: JamiTheme.invertedNormalButtonColor - - source: "qrc:/images/icons/group_add-24px.svg" - imageColor: JamiTheme.whiteColor - - toolTipText: JamiStrings.addParticipants - - onClicked: root.addToConferenceButtonClicked() - } - - PushButton { - id: chatButton - - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - - pressedColor: JamiTheme.invertedPressedButtonColor - hoveredColor: JamiTheme.invertedHoveredButtonColor - normalColor: JamiTheme.invertedNormalButtonColor - - source: "qrc:/images/icons/chat-24px.svg" - imageColor: JamiTheme.whiteColor - - toolTipText: JamiStrings.chat - - onClicked: root.chatButtonClicked() - } - - PushButton { - id: optionsButton - - Layout.preferredWidth: JamiTheme.callButtonPreferredSize - Layout.preferredHeight: JamiTheme.callButtonPreferredSize - Layout.rightMargin: 8 - - pressedColor: JamiTheme.invertedPressedButtonColor - hoveredColor: JamiTheme.invertedHoveredButtonColor - normalColor: JamiTheme.invertedNormalButtonColor - - source: "qrc:/images/icons/more_vert-24px.svg" - imageColor: JamiTheme.whiteColor - - toolTipText: JamiStrings.moreOptions - - onClicked: { - var rectPos = mapToItem(callStackViewWindow, optionsButton.x, optionsButton.y) - callViewContextMenu.x = rectPos.x + optionsButton.width / 2 - - callViewContextMenu.width / 2 - callViewContextMenu.y = rectPos.y - 12 - callViewContextMenu.height - callViewContextMenu.openMenu() - } - } - } -} diff --git a/src/mainview/components/MainOverlay.qml b/src/mainview/components/MainOverlay.qml new file mode 100644 index 0000000000000000000000000000000000000000..fd9632a81823ac17af2cc0530186479c3e9179e5 --- /dev/null +++ b/src/mainview/components/MainOverlay.qml @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2020-2021 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Andreas Traczyk <andreas.traczyk@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.14 +import QtQml 2.14 + +import net.jami.Models 1.0 +import net.jami.Adapters 1.0 +import net.jami.Constants 1.0 + +import "../../commoncomponents" + +Item { + id: root + + property string timeText: "00:00" + property string remoteRecordingLabel: "" + + property string bestName: "" + + property alias recordingVisible: recordingRect.visible + property alias callActionBar: __callActionBar + + property bool frozen: callActionBar.overflowOpen || + callActionBar.hovered || + callActionBar.subMenuOpen + + opacity: 0 + + // (un)subscribe to an app-wide mouse move event trap filtered + // for the overlay's geometry + onVisibleChanged: visible ? + CallOverlayModel.registerFilter(appWindow, this) : + CallOverlayModel.unregisterFilter(appWindow, this) + + Connections { + target: CallOverlayModel + + function onMouseMoved(item) { + if (item === root) { + root.opacity = 1 + fadeOutTimer.restart() + } + } + } + + // control overlay fade out. + Timer { + id: fadeOutTimer + interval: JamiTheme.overlayFadeDelay + onTriggered: { + if (frozen) + return + root.opacity = 0 + resetLabelsTimer.restart() + } + } + + // Timer to reset recording label and call duration time + Timer { + id: resetLabelsTimer + interval: 1000 + running: root.visible + repeat: true + onTriggered: { + root.timeText = CallAdapter.getCallDurationTime( + LRCInstance.currentAccountId, + LRCInstance.selectedConvUid) + if (root.opacity === 0 && !callViewContextMenu.peerIsRecording) + root.remoteRecordingLabel = "" + } + } + + Item { + id: overlayUpperPartRect + + anchors.top: parent.top + + width: parent.width + height: 50 + + RowLayout { + anchors.fill: parent + + Text { + id: jamiBestNameText + + Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft + Layout.preferredWidth: overlayUpperPartRect.width / 3 + Layout.preferredHeight: 50 + leftPadding: 16 + + font.pointSize: JamiTheme.textFontSize + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + text: textMetricsjamiBestNameText.elidedText + color: "white" + + TextMetrics { + id: textMetricsjamiBestNameText + font: jamiBestNameText.font + text: { + if (!root.isAudioOnly) { + if (remoteRecordingLabel === "") { + return root.bestName + } else { + return remoteRecordingLabel + } + } + return "" + } + elideWidth: overlayUpperPartRect.width / 3 + elide: Qt.ElideRight + } + } + + Text { + id: callTimerText + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.preferredWidth: 64 + Layout.minimumWidth: 64 + Layout.preferredHeight: 48 + Layout.rightMargin: recordingRect.visible? + 0 : JamiTheme.preferredMarginSize + font.pointSize: JamiTheme.textFontSize + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + text: textMetricscallTimerText.elidedText + color: "white" + TextMetrics { + id: textMetricscallTimerText + font: callTimerText.font + text: timeText + elideWidth: overlayUpperPartRect.width / 4 + elide: Qt.ElideRight + } + } + + Rectangle { + id: recordingRect + Layout.alignment: Qt.AlignVCenter | Qt.AlignRight + Layout.rightMargin: JamiTheme.preferredMarginSize + height: 16 + width: 16 + radius: height / 2 + color: "red" + + SequentialAnimation on color { + loops: Animation.Infinite + running: true + ColorAnimation { + from: "red"; to: "transparent"; + duration: JamiTheme.recordBlinkDuration + } + ColorAnimation { + from: "transparent"; to: "red"; + duration: JamiTheme.recordBlinkDuration + } + } + } + } + } + + CallActionBar { + id: __callActionBar + + anchors { + bottom: parent.bottom + bottomMargin: 26 + } + + width: parent.width + height: 55 + } + + Behavior on opacity { NumberAnimation { duration: JamiTheme.overlayFadeDuration }} +} diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml index 45bfbc0ef617e834e9bf9243c6315d75880e16ac..05584aa9dbc2ef3c46215537f45e3bf11ba45556 100644 --- a/src/mainview/components/OngoingCallPage.qml +++ b/src/mainview/components/OngoingCallPage.qml @@ -302,15 +302,11 @@ Rectangle { target: CallAdapter function onUpdateOverlay(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, - isRecording, isSIP, isConferenceCall, bestName) { + isRecording, isSIP, bestName) { callOverlay.showOnHoldImage(isPaused) audioCallPageRectCentralRect.visible = !isPaused && root.isAudioOnly - callOverlay.updateButtonStatus(isPaused, - isAudioOnly, - isAudioMuted, - isVideoMuted, - isRecording, isSIP, - isConferenceCall) + callOverlay.updateUI(isPaused, isAudioOnly, isAudioMuted, isVideoMuted, + isRecording, isSIP) root.bestName = bestName callOverlay.participantsLayer.update(CallAdapter.getConferencesInfos()) } @@ -329,7 +325,7 @@ Rectangle { } } - onOverlayChatButtonClicked: { + onChatButtonClicked: { inCallMessageWebViewStack.visible ? closeInCallConversation() : openInCallConversation() diff --git a/src/mainview/components/ParticipantsLayer.qml b/src/mainview/components/ParticipantsLayer.qml index 5a15616e702865eb7129f1ae575a81701d28ba48..6b39c40934d9d59bb35c89b58e78c7562459d01d 100644 --- a/src/mainview/components/ParticipantsLayer.qml +++ b/src/mainview/components/ParticipantsLayer.qml @@ -40,7 +40,7 @@ Item { // TODO: in the future the conference layout should be entirely managed by the client // Hack: truncate and ceil participant's overlay position and size to correct // when they are not exacts - callOverlay.updateMenu() + callOverlay.updateUI() var showMax = false var showMin = false diff --git a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml index 85754663f1a3b2fba20741c2d1651d698f76b3a7..985c0dbc23ed433734d6020b569085bb5b191464 100644 --- a/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml +++ b/src/mainview/components/VideoCallPageContextMenuDeviceItem.qml @@ -50,6 +50,6 @@ GeneralMenuItem { onClicked: { var deviceName = videoCallPageContextMenuDeviceItem.itemName - AvAdapter.onVideoContextMenuDeviceItemClicked(deviceName) + AvAdapter.selectVideoInputDeviceByName(deviceName) } } diff --git a/src/settingsview/components/VideoSettings.qml b/src/settingsview/components/VideoSettings.qml index cfa38815b198c518a3522b5f07e399e43770c254..4343d39f85e62d69230361d9eef5d587711aa978 100644 --- a/src/settingsview/components/VideoSettings.qml +++ b/src/settingsview/components/VideoSettings.qml @@ -63,7 +63,7 @@ ColumnLayout { } deviceComboBoxSetting.setCurrentIndex( - deviceComboBoxSetting.comboModel.getCurrentSettingIndex(), true) + deviceComboBoxSetting.comboModel.getCurrentIndex(), true) hardwareAccelControl.checked = AVModel.getHardwareAcceleration() } diff --git a/src/videoinputdevicemodel.cpp b/src/videoinputdevicemodel.cpp index b92c19b524f12bfda6c5a9d11db1add9313f0266..694becbfd246351b733d85d5bc4a809a6273223a 100644 --- a/src/videoinputdevicemodel.cpp +++ b/src/videoinputdevicemodel.cpp @@ -97,6 +97,8 @@ VideoInputDeviceModel::data(const QModelIndex& index, int role) const return QVariant((QString) currentDeviceSetting.size); case Role::DeviceName_UTF8: return QVariant(currentDeviceSetting.name.toUtf8()); + case Role::isCurrent: + return QVariant(index.row() == getCurrentIndex()); } return QVariant(); } @@ -111,6 +113,7 @@ VideoInputDeviceModel::roleNames() const roles[CurrentFrameRate] = "CurrentFrameRate"; roles[CurrentResolution] = "CurrentResolution"; roles[DeviceName_UTF8] = "DeviceName_UTF8"; + roles[isCurrent] = "isCurrent"; return roles; } @@ -159,15 +162,9 @@ VideoInputDeviceModel::deviceCount() } int -VideoInputDeviceModel::getCurrentSettingIndex() +VideoInputDeviceModel::getCurrentIndex() const { QString currentId = lrcInstance_->avModel().getCurrentVideoCaptureDevice(); auto resultList = match(index(0, 0), DeviceId, QVariant(currentId)); - - int resultRowIndex = 0; - if (resultList.size() > 0) { - resultRowIndex = resultList[0].row(); - } - - return resultRowIndex; + return resultList.size() > 0 ? resultList[0].row() : 0; } diff --git a/src/videoinputdevicemodel.h b/src/videoinputdevicemodel.h index 9278a1b6752901ac618da8260da7ca5c684f1c9e..ed9d42d263bedeb4e4f1eee8a66058bc8f6d6ffd 100644 --- a/src/videoinputdevicemodel.h +++ b/src/videoinputdevicemodel.h @@ -25,12 +25,13 @@ class VideoInputDeviceModel : public AbstractListModelBase Q_OBJECT public: enum Role { - DeviceChannel = Qt::UserRole + 1, - DeviceName, + DeviceName = Qt::UserRole + 1, + DeviceChannel, DeviceId, CurrentFrameRate, CurrentResolution, - DeviceName_UTF8 + DeviceName_UTF8, + isCurrent }; Q_ENUM(Role) @@ -59,8 +60,7 @@ public: * This function is to reset the model when there's new account added. */ Q_INVOKABLE int deviceCount(); - /* - * This function is to get the current device id in the demon. - */ - Q_INVOKABLE int getCurrentSettingIndex(); + + // get model index of the current device + Q_INVOKABLE int getCurrentIndex() const; };