diff --git a/images/icons/add_people_black_24dp.svg b/images/icons/add_people_black_24dp.svg
new file mode 100644
index 00000000..2bc314f7
--- /dev/null
+++ b/images/icons/add_people_black_24dp.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/images/icons/chat_black_24dp.svg b/images/icons/chat_black_24dp.svg
new file mode 100644
index 00000000..867ba122
--- /dev/null
+++ b/images/icons/chat_black_24dp.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/images/icons/record_black_24dp.svg b/images/icons/record_black_24dp.svg
new file mode 100644
index 00000000..8f5bb1ac
--- /dev/null
+++ b/images/icons/record_black_24dp.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/images/icons/share_screen_black_24dp.svg b/images/icons/share_screen_black_24dp.svg
new file mode 100644
index 00000000..de0e6a11
--- /dev/null
+++ b/images/icons/share_screen_black_24dp.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/images/icons/spk_black_24dp.svg b/images/icons/spk_black_24dp.svg
new file mode 100644
index 00000000..106a2711
--- /dev/null
+++ b/images/icons/spk_black_24dp.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/images/icons/spk_none_black_24dp.svg b/images/icons/spk_none_black_24dp.svg
new file mode 100644
index 00000000..0c2c89b6
--- /dev/null
+++ b/images/icons/spk_none_black_24dp.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/qml.qrc b/qml.qrc
index 5b974ba7..80b9a37d 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -106,7 +106,6 @@
src/mainview/components/CallStackView.qml
src/mainview/components/InitialCallPage.qml
src/mainview/components/CallOverlay.qml
- src/mainview/components/CallOverlayButtonGroup.qml
src/mainview/components/ContactSearchBar.qml
src/mainview/components/OngoingCallPage.qml
src/mainview/components/ParticipantOverlay.qml
@@ -137,5 +136,10 @@
src/mainview/components/SmartListItemDelegate.qml
src/mainview/components/BadgeNotifier.qml
src/mainview/components/ParticipantsLayer.qml
+ src/mainview/components/MainOverlay.qml
+ src/mainview/components/CallButtonDelegate.qml
+ src/mainview/components/CallActionBar.qml
+ src/commoncomponents/HalfPill.qml
+ src/commoncomponents/MaterialToolTip.qml
diff --git a/resources.qrc b/resources.qrc
index 07665875..bbfe9d50 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -134,5 +134,11 @@
images/icons/settings-24px.svg
images/icons/quote.svg
images/icons/plugins-24px.svg
+ images/icons/record_black_24dp.svg
+ images/icons/share_screen_black_24dp.svg
+ images/icons/chat_black_24dp.svg
+ images/icons/add_people_black_24dp.svg
+ images/icons/spk_black_24dp.svg
+ images/icons/spk_none_black_24dp.svg
diff --git a/src/audiodevicemodel.cpp b/src/audiodevicemodel.cpp
index 9dd71c82..9bb6e7d8 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 e8cf37e9..11a966cb 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 devices_;
Type type_ {Type::Invalid};
-};
\ No newline at end of file
+};
diff --git a/src/avadapter.cpp b/src/avadapter.cpp
index b9101139..d8d555ed 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 ff6a5604..c0347999 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 22fc61bb..57f70c1b 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 bdd7015d..06d9b84a 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 fb130de3..beb49d01 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 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(),
- CC_ROLES
-#undef X
- };
- primaryModel_->addItem(item);
+ primaryModel_->addItem(CallControl::Item {action.value()});
}
void
-CallOverlayModel::addSecondaryControl(const QVariantMap& props)
+CallOverlayModel::addSecondaryControl(const QVariant& action)
{
- CallControl::Item item {
-#define X(t, role) props[#role].value(),
- CC_ROLES
-#undef X
- };
- secondaryModel_->addItem(item);
+ secondaryModel_->addItem(CallControl::Item {action.value()});
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 856a71a3..1776184a 100644
--- a/src/calloverlaymodel.h
+++ b/src/calloverlaymodel.h
@@ -27,28 +27,15 @@
#include
#include
-#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 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 00000000..6d9564ee
--- /dev/null
+++ b/src/commoncomponents/HalfPill.qml
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 00000000..f7e1a410
--- /dev/null
+++ b/src/commoncomponents/MaterialToolTip.qml
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 a727c627..a703d23a 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 aa61f241..208c73b7 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 f70caac3..3cb860b1 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 00000000..67cdb061
--- /dev/null
+++ b/src/mainview/components/CallActionBar.qml
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 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 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 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 00000000..86e0f8e6
--- /dev/null
+++ b/src/mainview/components/CallButtonDelegate.qml
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2021 by Savoir-faire Linux
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 ea5b150f..8e99e8e9 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
* Author: Sébastien Blin
- * Author: Aline Gondim Santos
+ * Author: Aline Gondim Santos
*
* 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 {
+ function openContactPicker(type) {
+ ContactPickerCreation.createContactPickerObjects(type, root)
+ ContactPickerCreation.openContactPicker()
+ }
+
+ function openShareScreen() {
+ if (Qt.application.screens.length === 1) {
+ AvAdapter.shareEntireScreen(0)
+ } else {
+ SelectScreenWindowCreation.createSelectScreenWindowObject()
+ SelectScreenWindowCreation.showSelectScreenWindow()
+ }
+ }
+
+ function openShareScreenArea() {
+ if (Qt.platform.os !== "windows") {
+ AvAdapter.shareScreenArea(0, 0, 0, 0)
+ } else {
+ ScreenRubberBandCreation.createScreenRubberBandWindowObject()
+ ScreenRubberBandCreation.showScreenRubberBandWindow()
+ }
+ }
+
+ function openPluginsMenu() {
+ PluginHandlerPickerCreation.createPluginHandlerPickerObjects(root, true)
+ PluginHandlerPickerCreation.openPluginHandlerPicker()
+ }
+
+ MainOverlay {
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)
+ bestName: root.bestName
Connections {
- target: CallOverlayModel
-
- function onMouseMoved(item) {
- if (item === mainOverlay) {
- mainOverlay.opacity = 1
- fadeOutTimer.restart()
- }
- }
+ 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() }
}
-
- // control overlay fade out.
- Timer {
- id: fadeOutTimer
- interval: JamiTheme.overlayFadeDelay
- onTriggered: {
- if (callOverlayButtonGroup.hovered)
- return
- mainOverlay.opacity = 0
- resetLabelsTimer.restart()
- }
- }
-
- // 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 = ""
- }
- }
-
- 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 }
- }
- }
- }
- }
-
- CallOverlayButtonGroup {
- id: callOverlayButtonGroup
-
- anchors.bottom: parent.bottom
- anchors.bottomMargin: 10
- anchors.horizontalCenter: parent.horizontalCenter
-
- height: 56
- width: root.width
-
- onChatButtonClicked: {
- root.overlayChatButtonClicked()
- }
-
- onAddToConferenceButtonClicked: {
- // Create contact picker - conference.
- ContactPickerCreation.createContactPickerObjects(
- ContactList.CONFERENCE,
- root)
- ContactPickerCreation.openContactPicker()
- }
- }
-
- 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 4783fb1d..00000000
--- a/src/mainview/components/CallOverlayButtonGroup.qml
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2020 by Savoir-faire Linux
- * Author: Sébastien Blin
- * Author: Mingrui Zhang
- *
- * 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 .
- */
-
-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 00000000..fd9632a8
--- /dev/null
+++ b/src/mainview/components/MainOverlay.qml
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2020-2021 by Savoir-faire Linux
+ * Author: Mingrui Zhang
+ * Author: Sébastien Blin
+ * Author: Aline Gondim Santos
+ * Author: Andreas Traczyk
+ *
+ * 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 .
+ */
+
+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 45bfbc0e..05584aa9 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 5a15616e..6b39c409 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 85754663..985c0dbc 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 cfa38815..4343d39f 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 b92c19b5..694becbf 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 9278a1b6..ed9d42d2 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;
};