diff --git a/images/icons/cross_black_24dp.svg b/images/icons/cross_black_24dp.svg
new file mode 100644
index 00000000..3e5b5f55
--- /dev/null
+++ b/images/icons/cross_black_24dp.svg
@@ -0,0 +1,8 @@
+
+
+
diff --git a/qml.qrc b/qml.qrc
index 80b9a37d..b9c2e3bb 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -141,5 +141,7 @@
src/mainview/components/CallActionBar.qml
src/commoncomponents/HalfPill.qml
src/commoncomponents/MaterialToolTip.qml
+ src/mainview/components/ParticipantCallInStatusDelegate.qml
+ src/mainview/components/ParticipantCallInStatusView.qml
diff --git a/resources.qrc b/resources.qrc
index bbfe9d50..e5e4217a 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -5,6 +5,7 @@
images/icons/baseline-refresh-24px.svg
images/jami_rolling_spinner.gif
images/icons/baseline-close-24px.svg
+ images/icons/cross_black_24dp.svg
images/icons/baseline-done-24px.svg
images/icons/baseline-error_outline-24px.svg
projectcredits.html
diff --git a/src/calladapter.cpp b/src/calladapter.cpp
index 57f70c1b..cd5f72b6 100644
--- a/src/calladapter.cpp
+++ b/src/calladapter.cpp
@@ -643,29 +643,9 @@ CallAdapter::updateCallOverlay(const lrc::api::conversation::Info& convInfo)
}
void
-CallAdapter::hangupCall(const QString& uri)
+CallAdapter::hangUpCall(const QString& callId)
{
- const auto& convInfo = lrcInstance_->getConversationFromPeerUri(uri, accountId_);
- if (!convInfo.uid.isEmpty()) {
- auto callModel = lrcInstance_->getAccountInfo(accountId_).callModel.get();
- if (callModel->hasCall(convInfo.callId)) {
- /*
- * Store the last remaining participant of the conference,
- * so we can switch the smartlist index after termination.
- */
- if (!convInfo.confId.isEmpty()) {
- auto callList = lrcInstance_->getConferenceSubcalls(convInfo.confId);
- if (callList.size() == 2) {
- for (const auto& cId : callList) {
- if (cId != convInfo.callId) {
- lrcInstance_->pushlastConference(convInfo.confId, cId);
- }
- }
- }
- }
- callModel->hangUp(convInfo.callId);
- }
- }
+ lrcInstance_->getCurrentCallModel()->hangUp(callId);
}
void
diff --git a/src/calladapter.h b/src/calladapter.h
index 06d9b84a..1e56d3df 100644
--- a/src/calladapter.h
+++ b/src/calladapter.h
@@ -56,7 +56,7 @@ public:
Q_INVOKABLE void sipInputPanelPlayDTMF(const QString& key);
// For Call Overlay
- Q_INVOKABLE void hangupCall(const QString& uri);
+ Q_INVOKABLE void hangUpCall(const QString& callId);
Q_INVOKABLE void maximizeParticipant(const QString& uri);
Q_INVOKABLE void minimizeParticipant(const QString& uri);
Q_INVOKABLE void hangUpThisCall();
diff --git a/src/calloverlaymodel.cpp b/src/calloverlaymodel.cpp
index beb49d01..a450ac68 100644
--- a/src/calloverlaymodel.cpp
+++ b/src/calloverlaymodel.cpp
@@ -112,6 +112,127 @@ IndexRangeFilterProxyModel::setRange(int min, int max)
invalidateFilter();
}
+PendingConferenceesListModel::PendingConferenceesListModel(LRCInstance* instance, QObject* parent)
+ : QAbstractListModel(parent)
+ , lrcInstance_(instance)
+{
+ connectSignals();
+ connect(lrcInstance_, &LRCInstance::currentAccountIdChanged, [this]() { connectSignals(); });
+}
+
+int
+PendingConferenceesListModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return lrcInstance_->getCurrentCallModel()->getPendingConferencees().size();
+}
+
+QVariant
+PendingConferenceesListModel::data(const QModelIndex& index, int role) const
+{
+ using namespace PendingConferences;
+
+ // WARNING: not swarm ready
+ QString pendingConferenceeCallId;
+ QString pendingConferenceeContactUri;
+ ContactModel* contactModel {nullptr};
+ lrc::api::call::Status callStatus;
+ try {
+ auto callModel = lrcInstance_->getCurrentCallModel();
+ auto currentPendingConferenceeInfo = callModel->getPendingConferencees().at(index.row());
+ pendingConferenceeCallId = currentPendingConferenceeInfo.callId;
+ const auto call = callModel->getCall(pendingConferenceeCallId);
+
+ callStatus = call.status;
+ pendingConferenceeContactUri = currentPendingConferenceeInfo.uri;
+ contactModel = lrcInstance_->getCurrentContactModel();
+ } catch (...) {
+ return QVariant(false);
+ }
+
+ // Since we are using image provider right now, image url representation should be unique to
+ // be able to use the image cache, account avatar will only be updated once PictureUid changed
+ switch (role) {
+ case Role::PrimaryName:
+ return QVariant(contactModel->bestNameForContact(pendingConferenceeContactUri));
+ case Role::CallStatus:
+ return QVariant(lrc::api::call::to_string(callStatus));
+ case Role::ContactUri:
+ return QVariant(pendingConferenceeContactUri);
+ case Role::PendingConferenceeCallId:
+ return QVariant(pendingConferenceeCallId);
+ }
+ return QVariant();
+}
+
+QHash
+PendingConferenceesListModel::roleNames() const
+{
+ using namespace PendingConferences;
+ QHash roles;
+#define X(role) roles[role] = #role;
+ PC_ROLES
+#undef X
+ return roles;
+}
+
+void
+PendingConferenceesListModel::connectSignals()
+{
+ beginResetModel();
+
+ disconnect(callsStatusChanged_);
+ disconnect(beginInsertPendingConferencesRows_);
+ disconnect(endInsertPendingConferencesRows_);
+ disconnect(beginRemovePendingConferencesRows_);
+ disconnect(endRemovePendingConferencesRows_);
+
+ callsStatusChanged_ = connect(lrcInstance_->getCurrentCallModel(),
+ &NewCallModel::callStatusChanged,
+ this,
+ [this]() {
+ dataChanged(index(0, 0),
+ index(rowCount() - 1),
+ {PendingConferences::Role::CallStatus});
+ });
+
+ beginInsertPendingConferencesRows_ = connect(
+ lrcInstance_->getCurrentCallModel(),
+ &NewCallModel::beginInsertPendingConferenceesRows,
+ this,
+ [this](int position, int rows) {
+ beginInsertRows(QModelIndex(), position, position + (rows - 1));
+ },
+ Qt::DirectConnection);
+
+ endInsertPendingConferencesRows_ = connect(
+ lrcInstance_->getCurrentCallModel(),
+ &NewCallModel::endInsertPendingConferenceesRows,
+ this,
+ [this]() { endInsertRows(); },
+ Qt::DirectConnection);
+
+ beginRemovePendingConferencesRows_ = connect(
+ lrcInstance_->getCurrentCallModel(),
+ &NewCallModel::beginRemovePendingConferenceesRows,
+ this,
+ [this](int position, int rows) {
+ beginRemoveRows(QModelIndex(), position, position + (rows - 1));
+ },
+ Qt::DirectConnection);
+
+ endRemovePendingConferencesRows_ = connect(
+ lrcInstance_->getCurrentCallModel(),
+ &NewCallModel::endRemovePendingConferenceesRows,
+ this,
+ [this]() { endRemoveRows(); },
+
+ Qt::DirectConnection);
+
+ endResetModel();
+}
+
CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
: QObject(parent)
, lrcInstance_(instance)
@@ -120,6 +241,7 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
, overflowModel_(new IndexRangeFilterProxyModel(secondaryModel_))
, overflowVisibleModel_(new IndexRangeFilterProxyModel(secondaryModel_))
, overflowHiddenModel_(new IndexRangeFilterProxyModel(secondaryModel_))
+ , pendingConferenceesModel_(new PendingConferenceesListModel(instance, this))
{
connect(this,
&CallOverlayModel::overflowIndexChanged,
@@ -177,6 +299,12 @@ CallOverlayModel::overflowHiddenModel()
return QVariant::fromValue(overflowHiddenModel_);
}
+QVariant
+CallOverlayModel::pendingConferenceesModel()
+{
+ return QVariant::fromValue(pendingConferenceesModel_);
+}
+
void
CallOverlayModel::clearControls()
{
diff --git a/src/calloverlaymodel.h b/src/calloverlaymodel.h
index 1776184a..5aecc90e 100644
--- a/src/calloverlaymodel.h
+++ b/src/calloverlaymodel.h
@@ -27,6 +27,12 @@
#include
#include
+#define PC_ROLES \
+ X(PrimaryName) \
+ X(PendingConferenceeCallId) \
+ X(CallStatus) \
+ X(ContactUri)
+
namespace CallControl {
Q_NAMESPACE
enum Role { ItemAction = Qt::UserRole + 1, BadgeCount };
@@ -39,6 +45,17 @@ struct Item
};
} // namespace CallControl
+namespace PendingConferences {
+Q_NAMESPACE
+enum Role {
+ DummyRole = Qt::UserRole + 1,
+#define X(role) role,
+ PC_ROLES
+#undef X
+};
+Q_ENUM_NS(Role)
+} // namespace PendingConferences
+
class CallControlListModel : public QAbstractListModel
{
Q_OBJECT
@@ -73,6 +90,28 @@ private:
int max_ {-1};
};
+class PendingConferenceesListModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ PendingConferenceesListModel(LRCInstance* instance, QObject* parent = nullptr);
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ QHash roleNames() const override;
+
+ void connectSignals();
+
+private:
+ LRCInstance* lrcInstance_ {nullptr};
+
+ QMetaObject::Connection callsStatusChanged_;
+ QMetaObject::Connection beginInsertPendingConferencesRows_;
+ QMetaObject::Connection endInsertPendingConferencesRows_;
+ QMetaObject::Connection beginRemovePendingConferencesRows_;
+ QMetaObject::Connection endRemovePendingConferencesRows_;
+};
+
class CallOverlayModel : public QObject
{
Q_OBJECT
@@ -91,6 +130,7 @@ public:
Q_INVOKABLE QVariant overflowModel();
Q_INVOKABLE QVariant overflowVisibleModel();
Q_INVOKABLE QVariant overflowHiddenModel();
+ Q_INVOKABLE QVariant pendingConferenceesModel();
Q_INVOKABLE void registerFilter(QQuickWindow* object, QQuickItem* item);
Q_INVOKABLE void unregisterFilter(QQuickWindow* object, QQuickItem* item);
@@ -110,6 +150,7 @@ private:
IndexRangeFilterProxyModel* overflowModel_;
IndexRangeFilterProxyModel* overflowVisibleModel_;
IndexRangeFilterProxyModel* overflowHiddenModel_;
+ PendingConferenceesListModel* pendingConferenceesModel_;
QList watchedItems_;
};
diff --git a/src/commoncomponents/AvatarImage.qml b/src/commoncomponents/AvatarImage.qml
index 4a40cd5f..b10a8bb6 100644
--- a/src/commoncomponents/AvatarImage.qml
+++ b/src/commoncomponents/AvatarImage.qml
@@ -23,10 +23,10 @@ import net.jami.Adapters 1.0
import net.jami.Constants 1.0
import net.jami.Models 1.0
-Item {
+SpinningAnimation {
id: root
- enum Mode {
+ enum AvatarMode {
FromAccount = 0,
FromFile,
FromContactUri,
@@ -40,22 +40,22 @@ Item {
property alias sourceSize: rootImage.sourceSize
property int transitionDuration: 150
property bool saveToConfig: false
- property int mode: AvatarImage.Mode.FromAccount
+ property int avatarMode: AvatarImage.AvatarMode.FromAccount
property string imageProviderIdPrefix: {
- switch(mode) {
- case AvatarImage.Mode.FromAccount:
+ switch (avatarMode) {
+ case AvatarImage.AvatarMode.FromAccount:
return "account_"
- case AvatarImage.Mode.FromFile:
+ case AvatarImage.AvatarMode.FromFile:
return "file_"
- case AvatarImage.Mode.FromContactUri:
+ case AvatarImage.AvatarMode.FromContactUri:
return "contact_"
- case AvatarImage.Mode.FromConvUid:
+ case AvatarImage.AvatarMode.FromConvUid:
return "conversation_"
- case AvatarImage.Mode.FromTemporaryName:
+ case AvatarImage.AvatarMode.FromTemporaryName:
return "fallback_"
- case AvatarImage.Mode.FromBase64:
+ case AvatarImage.AvatarMode.FromBase64:
return "base64_"
- case AvatarImage.Mode.Default:
+ case AvatarImage.AvatarMode.Default:
return "default_"
default:
return ""
@@ -63,24 +63,23 @@ Item {
}
// Full request url example: forceUpdateUrl_xxxxxxx_account_xxxxxxxx
- property string imageProviderUrl: "image://avatarImage/" + forceUpdateUrl + "_" +
- imageProviderIdPrefix
+ property string imageProviderUrl: "image://avatarImage/" + forceUpdateUrl
+ + "_" + imageProviderIdPrefix
property string imageId: ""
property string forceUpdateUrl: Date.now()
property alias presenceStatus: presenceIndicator.status
property bool showPresenceIndicator: true
property int unreadMessagesCount: 0
- property bool enableAnimation: true
- property bool showSpinningAnimation: false
+ property bool enableFadeAnimation: true
signal imageIsReady
function saveAvatarToConfig() {
- switch(mode) {
- case AvatarImage.Mode.FromFile:
+ switch (avatarMode) {
+ case AvatarImage.AvatarMode.FromFile:
AccountAdapter.setCurrAccAvatar(true, imageId)
break
- case AvatarImage.Mode.FromBase64:
+ case AvatarImage.AvatarMode.FromBase64:
AccountAdapter.setCurrAccAvatar(false, imageId)
break
default:
@@ -102,60 +101,38 @@ Item {
}
function reloadImageSource() {
- var tempEnableAnimation = enableAnimation
+ var tempEnableAnimation = enableFadeAnimation
var tempImageSource = rootImage.source
- enableAnimation = false
+ enableFadeAnimation = false
rootImage.source = ""
- enableAnimation = tempEnableAnimation
+ enableFadeAnimation = tempEnableAnimation
rootImage.source = tempImageSource
}
function rootImageOverlayReadyCallback() {
- if (rootImageOverlay.status === Image.Ready &&
- (rootImageOverlay.state === "avatarImgFadeIn")) {
- rootImageOverlay.statusChanged.disconnect(rootImageOverlayReadyCallback)
+ if (rootImageOverlay.status === Image.Ready
+ && (rootImageOverlay.state === "avatarImgFadeIn")) {
+ rootImageOverlay.statusChanged.disconnect(
+ rootImageOverlayReadyCallback)
rootImageOverlay.state = ''
}
}
- Image {
- id: rootImage
+ Item {
+ id: imageGroup
- anchors.fill: root
+ anchors.centerIn: root
- smooth: true
- antialiasing: true
- asynchronous: true
-
- sourceSize.width: Math.max(24, width)
- sourceSize.height: Math.max(24, height)
-
- fillMode: Image.PreserveAspectFit
-
- onStatusChanged: {
- if (status === Image.Ready) {
- if (enableAnimation) {
- rootImageOverlay.state = "avatarImgFadeIn"
- } else {
- rootImageOverlay.source = rootImage.source
- root.imageIsReady()
- }
- }
- }
-
- Component.onCompleted: {
- if (imageId)
- return source = imageProviderUrl + imageId
- return source = ""
- }
+ width: root.width - spinningAnimationWidth
+ height: root.height - spinningAnimationWidth
Image {
- id: rootImageOverlay
+ id: rootImage
- anchors.fill: rootImage
+ anchors.fill: imageGroup
smooth: true
antialiasing: true
@@ -166,65 +143,87 @@ Item {
fillMode: Image.PreserveAspectFit
- states: State {
- name: "avatarImgFadeIn"
- PropertyChanges {
- target: rootImageOverlay
- opacity: 0
+ onStatusChanged: {
+ if (status === Image.Ready) {
+ if (enableFadeAnimation) {
+ rootImageOverlay.state = "avatarImgFadeIn"
+ } else {
+ rootImageOverlay.source = rootImage.source
+ root.imageIsReady()
+ }
}
}
- transitions: Transition {
- NumberAnimation {
- properties: "opacity"
- easing.type: Easing.InOutQuad
- duration: enableAnimation ? 400 : 0
+ Component.onCompleted: {
+ if (imageId)
+ return source = imageProviderUrl + imageId
+ return source = ""
+ }
+
+ Image {
+ id: rootImageOverlay
+
+ anchors.fill: rootImage
+
+ smooth: true
+ antialiasing: true
+ asynchronous: true
+
+ sourceSize.width: Math.max(24, width)
+ sourceSize.height: Math.max(24, height)
+
+ fillMode: Image.PreserveAspectFit
+
+ states: State {
+ name: "avatarImgFadeIn"
+ PropertyChanges {
+ target: rootImageOverlay
+ opacity: 0
+ }
}
- onRunningChanged: {
- if ((rootImageOverlay.state === "avatarImgFadeIn") && (!running)) {
- if (rootImageOverlay.source === rootImage.source) {
- rootImageOverlay.state = ''
- return
+ transitions: Transition {
+ NumberAnimation {
+ properties: "opacity"
+ easing.type: Easing.InOutQuad
+ duration: enableFadeAnimation ? 400 : 0
+ }
+
+ onRunningChanged: {
+ if ((rootImageOverlay.state === "avatarImgFadeIn")
+ && (!running)) {
+ if (rootImageOverlay.source === rootImage.source) {
+ rootImageOverlay.state = ''
+ return
+ }
+ rootImageOverlay.statusChanged.connect(
+ rootImageOverlayReadyCallback)
+ rootImageOverlay.source = rootImage.source
}
- rootImageOverlay.statusChanged.connect(rootImageOverlayReadyCallback)
- rootImageOverlay.source = rootImage.source
}
}
}
}
- }
- PresenceIndicator {
- id: presenceIndicator
+ PresenceIndicator {
+ id: presenceIndicator
- anchors.right: root.right
- anchors.rightMargin: -1
- anchors.bottom: root.bottom
- anchors.bottomMargin: -1
+ anchors.right: imageGroup.right
+ anchors.rightMargin: -1
+ anchors.bottom: imageGroup.bottom
+ anchors.bottomMargin: -1
- size: root.width * 0.26
+ size: imageGroup.width * 0.26
- visible: showPresenceIndicator
- }
+ visible: showPresenceIndicator
+ }
- SpinningAnimation {
- id: spinningAnimation
+ Connections {
+ target: ScreenInfo
- anchors.horizontalCenter: root.horizontalCenter
- anchors.verticalCenter: root.verticalCenter
-
- visible: showSpinningAnimation
- width: Math.ceil(root.width * 1.05)
- height: Math.ceil(root.height * 1.05)
- z: -1
- }
-
- Connections {
- target: ScreenInfo
-
- function onDevicePixelRatioChanged(){
- reloadImageSource()
+ function onDevicePixelRatioChanged() {
+ reloadImageSource()
+ }
}
}
}
diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml
index 5f0e35c5..19434d54 100644
--- a/src/commoncomponents/PhotoboothView.qml
+++ b/src/commoncomponents/PhotoboothView.qml
@@ -49,7 +49,7 @@ ColumnLayout {
photoState = PhotoboothView.PhotoState.Default
avatarSet = false
if (useDefaultAvatar)
- setAvatarImage(AvatarImage.Mode.Default, "")
+ setAvatarImage(AvatarImage.AvatarMode.Default, "")
}
function startBooth() {
@@ -65,16 +65,16 @@ ColumnLayout {
} catch(erro){console.log("Exception: " + erro.message)}
}
- function setAvatarImage(mode = AvatarImage.Mode.FromAccount,
+ function setAvatarImage(mode = AvatarImage.AvatarMode.FromAccount,
imageId = LRCInstance.currentAccountId){
- if (mode !== AvatarImage.Mode.FromBase64)
- avatarImg.enableAnimation = true
+ if (mode !== AvatarImage.AvatarMode.FromBase64)
+ avatarImg.enableFadeAnimation = true
else
- avatarImg.enableAnimation = false
+ avatarImg.enableFadeAnimation = false
- avatarImg.mode = mode
+ avatarImg.avatarMode = mode
- if (mode === AvatarImage.Mode.Default) {
+ if (mode === AvatarImage.AvatarMode.Default) {
avatarImg.updateImage(imageId)
return
}
@@ -116,7 +116,7 @@ ColumnLayout {
return
}
- setAvatarImage(AvatarImage.Mode.FromFile,
+ setAvatarImage(AvatarImage.AvatarMode.FromFile,
UtilsAdapter.getAbsPath(fileName))
}
}
@@ -161,7 +161,7 @@ ColumnLayout {
}
onImageIsReady: {
- if (mode === AvatarImage.Mode.FromBase64)
+ if (avatarMode === AvatarImage.AvatarMode.FromBase64)
photoState = PhotoboothView.PhotoState.Taken
if (photoState === PhotoboothView.PhotoState.Taken) {
@@ -268,7 +268,7 @@ ColumnLayout {
startBooth()
return
} else {
- setAvatarImage(AvatarImage.Mode.FromBase64,
+ setAvatarImage(AvatarImage.AvatarMode.FromBase64,
previewWidget.takePhoto(boothWidth))
avatarSet = true
diff --git a/src/commoncomponents/SpinningAnimation.qml b/src/commoncomponents/SpinningAnimation.qml
index 6147893a..f8061b23 100644
--- a/src/commoncomponents/SpinningAnimation.qml
+++ b/src/commoncomponents/SpinningAnimation.qml
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2021 by Savoir-faire Linux
* Author: Aline Gondim Santos
+ * 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
@@ -25,8 +26,23 @@ import QtGraphicalEffects 1.12
Item {
id: root
+ enum SpinningAnimationMode {
+ DISABLED = 0,
+ NORMAL,
+ SYMMETRY
+ }
+
+ property int spinningAnimationMode: SpinningAnimation.SpinningAnimationMode.DISABLED
+ property int spinningAnimationWidth: 5
+ property real outerCutRadius: root.height / 2
+ property int spinningAnimationDuration: 1000
+
ConicalGradient {
+ id: conicalGradientOne
+
anchors.fill: parent
+
+ visible: spinningAnimationMode !== SpinningAnimation.SpinningAnimationMode.DISABLED
angle: 0.0
gradient: Gradient {
GradientStop { position: 0.5; color: "transparent" }
@@ -35,17 +51,74 @@ Item {
RotationAnimation on angle {
loops: Animation.Infinite
- duration: 1000
+ duration: spinningAnimationDuration
from: 0
to: 360
}
- }
- layer.enabled: true
- layer.effect: OpacityMask {
- maskSource: Rectangle {
- width: root.height
- height: root.height
- radius: root.height / 2
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ invert: true
+ maskSource: Item {
+ width: conicalGradientOne.width
+ height: conicalGradientOne.height
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: spinningAnimationWidth
+ radius: outerCutRadius
+ }
+ }
}
}
-}
\ No newline at end of file
+
+ ConicalGradient {
+ id: conicalGradientTwo
+
+ anchors.fill: parent
+
+ visible: spinningAnimationMode === SpinningAnimation.SpinningAnimationMode.SYMMETRY
+ angle: 180.0
+ gradient: Gradient {
+ GradientStop {
+ position: 0.75
+ color: "transparent"
+ }
+ GradientStop {
+ position: 1.0
+ color: "white"
+ }
+ }
+
+ RotationAnimation on angle {
+ loops: Animation.Infinite
+ duration: spinningAnimationDuration
+ from: 180.0
+ to: 540.0
+ }
+
+ layer.enabled: true
+ layer.effect: OpacityMask {
+ invert: true
+ maskSource: Item {
+ width: conicalGradientTwo.width
+ height: conicalGradientTwo.height
+
+ Rectangle {
+ anchors.fill: parent
+ anchors.margins: spinningAnimationWidth
+ radius: outerCutRadius
+ }
+ }
+ }
+ }
+
+ layer.enabled: spinningAnimationMode !== SpinningAnimation.SpinningAnimationMode.DISABLED
+ layer.effect: OpacityMask {
+ maskSource: Rectangle {
+ width: root.width
+ height: root.height
+ radius: outerCutRadius
+ }
+ }
+}
diff --git a/src/constant/JamiTheme.qml b/src/constant/JamiTheme.qml
index 3cb860b1..a3357ba0 100644
--- a/src/constant/JamiTheme.qml
+++ b/src/constant/JamiTheme.qml
@@ -129,6 +129,9 @@ Item {
// Plugin Preferences View
property color comboBoxBackgroundColor: darkTheme ? editBackgroundColor : selectedColor
+ // ParticipantCallInStatusView
+ property color participantCallInStatusTextColor: whiteColor
+
// Chatview
property color jamiLightBlue: darkTheme ? "#003b4e" : Qt.rgba(59, 193, 211, 0.3)
property color jamiDarkBlue: darkTheme ? "#28b1ed" : "#003b4e"
@@ -191,6 +194,16 @@ Item {
property real smartListAvatarSize: 52
property real avatarSizeInCall: 130
property real callButtonPreferredSize: 50
+ property int participantCallInStatusViewWidth: 225
+ property int participantCallInStatusViewHeight: 300
+ property int participantCallInStatusDelegateHeight: 105
+ property int participantCallInStatusDelegateRadius: 5
+ property real participantCallInStatusOpacity: 0.77
+ property int participantCallInAvatarSize: 75
+ property int participantCallInNameFontSize: 11
+ property int participantCallInStatusFontSize: 9
+ property int participantCallInStatusTextWidthLimit: 100
+ property int participantCallInStatusTextWidth: 68
property real maximumWidthSettingsView: 600
property real settingsHeaderpreferredHeight: 64
diff --git a/src/mainview/components/ContactPickerItemDelegate.qml b/src/mainview/components/ContactPickerItemDelegate.qml
index d71bd2bc..6c9a7d32 100644
--- a/src/mainview/components/ContactPickerItemDelegate.qml
+++ b/src/mainview/components/ContactPickerItemDelegate.qml
@@ -39,7 +39,7 @@ ItemDelegate {
width: 40
height: 40
- mode: AvatarImage.Mode.FromContactUri
+ avatarMode: AvatarImage.AvatarMode.FromContactUri
imageId: URI
}
diff --git a/src/mainview/components/InitialCallPage.qml b/src/mainview/components/InitialCallPage.qml
index 9c22c21e..1c1484ba 100644
--- a/src/mainview/components/InitialCallPage.qml
+++ b/src/mainview/components/InitialCallPage.qml
@@ -34,7 +34,7 @@ Rectangle {
property bool isIncoming: false
property bool isAudioOnly: false
- property var accountConvPair: ["",""]
+ property var accountConvPair: ["", ""]
property int callStatus: 0
property string bestName: ""
@@ -76,12 +76,12 @@ Rectangle {
id: contactImg
Layout.alignment: Qt.AlignHCenter
- Layout.preferredWidth: JamiTheme.avatarSizeInCall
- Layout.preferredHeight: JamiTheme.avatarSizeInCall
+ Layout.preferredWidth: JamiTheme.avatarSizeInCall + spinningAnimationWidth
+ Layout.preferredHeight: JamiTheme.avatarSizeInCall + spinningAnimationWidth
- mode: AvatarImage.Mode.FromConvUid
+ avatarMode: AvatarImage.AvatarMode.FromConvUid
showPresenceIndicator: false
- showSpinningAnimation: true
+ spinningAnimationMode: SpinningAnimation.SpinningAnimationMode.NORMAL
}
Text {
diff --git a/src/mainview/components/MainOverlay.qml b/src/mainview/components/MainOverlay.qml
index fd9632a8..d47de099 100644
--- a/src/mainview/components/MainOverlay.qml
+++ b/src/mainview/components/MainOverlay.qml
@@ -44,7 +44,8 @@ Item {
property bool frozen: callActionBar.overflowOpen ||
callActionBar.hovered ||
- callActionBar.subMenuOpen
+ callActionBar.subMenuOpen ||
+ participantCallInStatusView.visible
opacity: 0
@@ -184,6 +185,15 @@ Item {
}
}
+ ParticipantCallInStatusView {
+ id: participantCallInStatusView
+
+ anchors.right: root.right
+ anchors.rightMargin: 10
+ anchors.bottom: __callActionBar.top
+ anchors.bottomMargin: 20
+ }
+
CallActionBar {
id: __callActionBar
diff --git a/src/mainview/components/OngoingCallPage.qml b/src/mainview/components/OngoingCallPage.qml
index c4cec1c0..a68f2f42 100644
--- a/src/mainview/components/OngoingCallPage.qml
+++ b/src/mainview/components/OngoingCallPage.qml
@@ -348,7 +348,7 @@ Rectangle {
Layout.preferredWidth: JamiTheme.avatarSizeInCall
Layout.preferredHeight: JamiTheme.avatarSizeInCall
- mode: AvatarImage.Mode.FromConvUid
+ avatarMode: AvatarImage.AvatarMode.FromConvUid
showPresenceIndicator: false
}
diff --git a/src/mainview/components/ParticipantCallInStatusDelegate.qml b/src/mainview/components/ParticipantCallInStatusDelegate.qml
new file mode 100644
index 00000000..d73c3991
--- /dev/null
+++ b/src/mainview/components/ParticipantCallInStatusDelegate.qml
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * 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 net.jami.Adapters 1.0
+import net.jami.Models 1.0
+import net.jami.Constants 1.0
+
+import "../../commoncomponents"
+
+SpinningAnimation {
+ id: root
+
+ width: contentRect.width + spinningAnimationWidth
+ height: JamiTheme.participantCallInStatusDelegateHeight
+
+ spinningAnimationMode: SpinningAnimation.SpinningAnimationMode.SYMMETRY
+ outerCutRadius: JamiTheme.participantCallInStatusDelegateRadius
+ spinningAnimationDuration: 5000
+
+ Rectangle {
+ id: contentRect
+
+ anchors.centerIn: root
+
+ width: JamiTheme.participantCallInStatusViewWidth + callStatus.Layout.preferredWidth
+ - JamiTheme.participantCallInStatusTextWidth - spinningAnimationWidth
+ height: JamiTheme.participantCallInStatusDelegateHeight - spinningAnimationWidth
+
+ color: JamiTheme.darkGreyColor
+ opacity: JamiTheme.participantCallInStatusOpacity
+ radius: JamiTheme.participantCallInStatusDelegateRadius
+
+ AvatarImage {
+ id: avatar
+
+ anchors.left: contentRect.left
+ anchors.leftMargin: 10
+ anchors.verticalCenter: contentRect.verticalCenter
+
+ width: JamiTheme.participantCallInAvatarSize
+ height: JamiTheme.participantCallInAvatarSize
+
+ showPresenceIndicator: false
+ avatarMode: AvatarImage.AvatarMode.FromContactUri
+ imageId: ContactUri
+ }
+
+ ColumnLayout {
+ id: infoColumnLayout
+
+ anchors.left: avatar.right
+ anchors.leftMargin: 5
+ anchors.verticalCenter: contentRect.verticalCenter
+
+ implicitHeight: 50
+ implicitWidth: JamiTheme.participantCallInStatusTextWidth
+
+ spacing: 5
+
+ Text {
+ id: name
+
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+ Layout.preferredWidth: JamiTheme.participantCallInStatusTextWidth
+
+ font.weight: Font.ExtraBold
+ font.pointSize: JamiTheme.participantCallInNameFontSize
+ color: JamiTheme.participantCallInStatusTextColor
+ text: PrimaryName
+ elide: Text.ElideRight
+ }
+
+ Text {
+ id: callStatus
+
+ Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
+
+ font.weight: Font.Normal
+ font.pointSize: JamiTheme.participantCallInStatusFontSize
+ color: JamiTheme.participantCallInStatusTextColor
+ text: CallStatus + "…"
+ elide: Text.ElideRight
+
+ onWidthChanged: {
+ if (width > JamiTheme.participantCallInStatusTextWidth
+ && width < JamiTheme.participantCallInStatusTextWidthLimit)
+ callStatus.Layout.preferredWidth = width
+ else if (width >= JamiTheme.participantCallInStatusTextWidthLimit)
+ callStatus.Layout.preferredWidth
+ = JamiTheme.participantCallInStatusTextWidthLimit
+ else
+ callStatus.Layout.preferredWidth
+ = JamiTheme.participantCallInStatusTextWidth
+ }
+ }
+ }
+
+ PushButton {
+ id: callCancelButton
+
+ anchors.right: contentRect.right
+ anchors.rightMargin: 10
+ anchors.verticalCenter: contentRect.verticalCenter
+
+ width: 40
+ height: 40
+ // To control the size of the svg
+ preferredSize: 50
+
+ pressedColor: JamiTheme.refuseRed
+ hoveredColor: JamiTheme.refuseRed
+ normalColor: JamiTheme.refuseRedTransparent
+
+ source: "qrc:/images/icons/cross_black_24dp.svg"
+ imageColor: JamiTheme.whiteColor
+
+ toolTipText: JamiStrings.optionCancel
+
+ onClicked: CallAdapter.hangUpCall(PendingConferenceeCallId)
+ }
+ }
+}
diff --git a/src/mainview/components/ParticipantCallInStatusView.qml b/src/mainview/components/ParticipantCallInStatusView.qml
new file mode 100644
index 00000000..81790813
--- /dev/null
+++ b/src/mainview/components/ParticipantCallInStatusView.qml
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 by Savoir-faire Linux
+ * 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 net.jami.Models 1.0
+import net.jami.Adapters 1.0
+import net.jami.Constants 1.0
+
+ListView {
+ id: root
+
+ width: currentItem ? currentItem.width + currentItem.spinningAnimationWidth
+ : JamiTheme.participantCallInStatusViewWidth
+ height: JamiTheme.participantCallInStatusDelegateHeight
+
+ model: CallOverlayModel.pendingConferenceesModel()
+ delegate: ParticipantCallInStatusDelegate {}
+
+ visible: currentItem ? true : false
+
+ Connections {
+ target: model
+
+ function onRowsInserted() {
+ var preferredHeight = JamiTheme.participantCallInStatusDelegateHeight * model.rowCount()
+ root.height = JamiTheme.participantCallInStatusViewHeight
+ < preferredHeight ? JamiTheme.participantCallInStatusViewHeight
+ : preferredHeight
+ }
+
+ function onRowsRemoved() {
+ var preferredHeight = JamiTheme.participantCallInStatusDelegateHeight * model.rowCount()
+ root.height = JamiTheme.participantCallInStatusViewHeight
+ < preferredHeight ? JamiTheme.participantCallInStatusViewHeight
+ : preferredHeight
+ }
+ }
+
+ clip: true
+ maximumFlickVelocity: 1024
+ ScrollIndicator.vertical: ScrollIndicator {}
+}
diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml
index 7d166ae1..6120491f 100644
--- a/src/mainview/components/ParticipantOverlay.qml
+++ b/src/mainview/components/ParticipantOverlay.qml
@@ -58,16 +58,16 @@ Item {
contactImage.visible = false
else {
if (avatar) {
- contactImage.mode = AvatarImage.Mode.FromBase64
+ contactImage.avatarMode = AvatarImage.AvatarMode.FromBase64
contactImage.updateImage(avatar)
} else if (local) {
- contactImage.mode = AvatarImage.Mode.FromAccount
+ contactImage.avatarMode = AvatarImage.AvatarMode.FromAccount
contactImage.updateImage(LRCInstance.currentAccountId)
} else if (isContact) {
- contactImage.mode = AvatarImage.Mode.FromContactUri
+ contactImage.avatarMode = AvatarImage.AvatarMode.FromContactUri
contactImage.updateImage(uri)
} else {
- contactImage.mode = AvatarImage.Mode.FromTemporaryName
+ contactImage.avatarMode = AvatarImage.AvatarMode.FromTemporaryName
contactImage.updateImage(uri)
}
contactImage.visible = true
@@ -195,7 +195,7 @@ Item {
fillMode: Image.PreserveAspectFit
imageId: ""
visible: false
- mode: AvatarImage.Mode.Default
+ avatarMode: AvatarImage.AvatarMode.Default
showPresenceIndicator: false
layer.enabled: true
diff --git a/src/mainview/components/SmartListItemDelegate.qml b/src/mainview/components/SmartListItemDelegate.qml
index 02f449d2..263d9981 100644
--- a/src/mainview/components/SmartListItemDelegate.qml
+++ b/src/mainview/components/SmartListItemDelegate.qml
@@ -68,7 +68,7 @@ ItemDelegate {
Layout.preferredWidth: JamiTheme.smartListAvatarSize
Layout.preferredHeight: JamiTheme.smartListAvatarSize
- mode: AvatarImage.Mode.FromContactUri
+ avatarMode: AvatarImage.AvatarMode.FromContactUri
showPresenceIndicator: Presence === undefined ? false : Presence
transitionDuration: 0
}
diff --git a/src/mainview/components/UserProfile.qml b/src/mainview/components/UserProfile.qml
index ac0690dc..0e0f6b87 100644
--- a/src/mainview/components/UserProfile.qml
+++ b/src/mainview/components/UserProfile.qml
@@ -66,7 +66,7 @@ BaseDialog {
sourceSize.width: preferredImgSize
sourceSize.height: preferredImgSize
- mode: AvatarImage.Mode.FromConvUid
+ avatarMode: AvatarImage.AvatarMode.FromConvUid
showPresenceIndicator: false
}
diff --git a/src/settingsview/components/ContactItemDelegate.qml b/src/settingsview/components/ContactItemDelegate.qml
index 92a8ce1c..36faaae8 100644
--- a/src/settingsview/components/ContactItemDelegate.qml
+++ b/src/settingsview/components/ContactItemDelegate.qml
@@ -65,7 +65,7 @@ ItemDelegate {
anchors.fill: parent
- mode: AvatarImage.Mode.FromContactUri
+ avatarMode: AvatarImage.AvatarMode.FromContactUri
showPresenceIndicator: false
fillMode: Image.PreserveAspectCrop
diff --git a/src/wizardview/components/ProfilePage.qml b/src/wizardview/components/ProfilePage.qml
index 79b6c164..d308c075 100644
--- a/src/wizardview/components/ProfilePage.qml
+++ b/src/wizardview/components/ProfilePage.qml
@@ -54,7 +54,7 @@ Rectangle {
color: JamiTheme.backgroundColor
onCreatedAccountIdChanged: {
- setAvatarWidget.setAvatarImage(AvatarImage.Mode.FromAccount,
+ setAvatarWidget.setAvatarImage(AvatarImage.AvatarMode.FromAccount,
createdAccountId)
}
@@ -126,14 +126,14 @@ Rectangle {
onTextEdited: {
if (!(setAvatarWidget.avatarSet)) {
if (text.length === 0) {
- setAvatarWidget.setAvatarImage(AvatarImage.Mode.FromAccount,
+ setAvatarWidget.setAvatarImage(AvatarImage.AvatarMode.FromAccount,
createdAccountId)
return
}
if (text.length == 1 && text.charAt(0) !== lastInitialCharacter) {
lastInitialCharacter = text.charAt(0)
- setAvatarWidget.setAvatarImage(AvatarImage.Mode.FromTemporaryName,
+ setAvatarWidget.setAvatarImage(AvatarImage.AvatarMode.FromTemporaryName,
text)
}
}