diff --git a/src/app/calladapter.cpp b/src/app/calladapter.cpp index ddc608d7a228fb581fb1e88d7e7c6d9ed454d141..58862ef6104b132c96bbb8089ba0ff3d880afe0d 100644 --- a/src/app/calladapter.cpp +++ b/src/app/calladapter.cpp @@ -33,6 +33,7 @@ #include <QJsonObject> #include <api/callparticipantsmodel.h> +#include <api/devicemodel.h> #include <media_const.h> @@ -42,16 +43,7 @@ CallAdapter::CallAdapter(SystemTray* systemTray, LRCInstance* instance, QObject* { participantsModel_.reset(new CallParticipantsModel(lrcInstance_, this)); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, participantsModel_.get(), "CallParticipantsModel"); - participantsModelFiltered_.reset( - new GenericParticipantsFilterModel(lrcInstance_, participantsModel_.get())); - QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, - participantsModelFiltered_.get(), - "GenericParticipantsFilterModel"); - activeParticipantsModel_.reset( - new ActiveParticipantsFilterModel(lrcInstance_, participantsModel_.get())); - QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, - activeParticipantsModel_.get(), - "ActiveParticipantsFilterModel"); + overlayModel_.reset(new CallOverlayModel(lrcInstance_, this)); QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, overlayModel_.get(), "CallOverlayModel"); @@ -517,18 +509,35 @@ CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool f void CallAdapter::fillParticipantData(QJsonObject& participant) const { - participant[lrc::api::ParticipantsInfosStrings::BESTNAME] - = participant[lrc::api::ParticipantsInfosStrings::URI]; + // TODO: getCurrentDeviceId should be part of CurrentAccount, and Current<thing> + // should be read accessible through LRCInstance ?? + auto getCurrentDeviceId = [](const account::Info& accInfo) -> QString { + const auto& deviceList = accInfo.deviceModel->getAllDevices(); + auto devIt = std::find_if(std::cbegin(deviceList), + std::cend(deviceList), + [](const Device& dev) { return dev.isCurrent; }); + return devIt != deviceList.cend() ? devIt->id : QString(); + }; + auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_); - participant[lrc::api::ParticipantsInfosStrings::ISLOCAL] = false; - if (participant[lrc::api::ParticipantsInfosStrings::BESTNAME] == accInfo.profileInfo.uri) { - participant[lrc::api::ParticipantsInfosStrings::BESTNAME] = tr("me"); - participant[lrc::api::ParticipantsInfosStrings::ISLOCAL] = true; + using namespace lrc::api::ParticipantsInfosStrings; + + // If both the URI and the device Id match, we set the "is local" flag + // used to filter out the participant. + // TODO: + // - This filter should always be applied, and any local streams should render + // using local sinks. Local non-preview participants should have proxy participant + // items replaced into this model using their local sink Ids. + // - The app setting should remain and be used to control whether or not the preview + // sink partipcant is rendered. + auto uri = participant[URI].toString(); + participant[ISLOCAL] = false; + if (uri == accInfo.profileInfo.uri && participant[DEVICE] == getCurrentDeviceId(accInfo)) { + participant[BESTNAME] = tr("me"); + participant[ISLOCAL] = true; } else { try { - participant[lrc::api::ParticipantsInfosStrings::BESTNAME] - = lrcInstance_->getCurrentAccountInfo().contactModel->bestNameForContact( - participant[lrc::api::ParticipantsInfosStrings::URI].toString()); + participant[BESTNAME] = accInfo.contactModel->bestNameForContact(uri); } catch (...) { } } diff --git a/src/app/calladapter.h b/src/app/calladapter.h index 254ca3b84f41bda65b62080625b09966d12cf3cd..0e18614b5f531c30a292fcdd9a8a8ec8ddfcbba3 100644 --- a/src/app/calladapter.h +++ b/src/app/calladapter.h @@ -132,7 +132,5 @@ private: SystemTray* systemTray_; QScopedPointer<CallOverlayModel> overlayModel_; QScopedPointer<CallParticipantsModel> participantsModel_; - QScopedPointer<GenericParticipantsFilterModel> participantsModelFiltered_; - QScopedPointer<ActiveParticipantsFilterModel> activeParticipantsModel_; VectorString currentConfSubcalls_; }; diff --git a/src/app/callparticipantsmodel.cpp b/src/app/callparticipantsmodel.cpp index 1fd8ace59b2b25a674b2205cf056195b3fb153b0..48a8544febaa200ac654c8956a6c3630c9728e04 100644 --- a/src/app/callparticipantsmodel.cpp +++ b/src/app/callparticipantsmodel.cpp @@ -51,56 +51,42 @@ CallParticipantsModel::data(const QModelIndex& index, int role) const using namespace CallParticipant; // Internal call, so no need to protect participants_ as locked higher - auto participant = participants_.at(index.row()); + auto& item = participants_.at(index.row()).item; + using namespace ParticipantsInfosStrings; switch (role) { case Role::Uri: - return QVariant::fromValue(participant.item.value(lrc::api::ParticipantsInfosStrings::URI)); + return QVariant(item.value(URI).toString()); case Role::BestName: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::BESTNAME)); + return QVariant(item.value(BESTNAME).toString()); case Role::Device: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::DEVICE)); + return QVariant(item.value(DEVICE).toString()); case Role::Active: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::ACTIVE)); + return QVariant(item.value(ACTIVE).toBool()); case Role::AudioLocalMuted: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::AUDIOLOCALMUTED)); + return QVariant(item.value(AUDIOLOCALMUTED).toBool()); case Role::AudioModeratorMuted: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::AUDIOMODERATORMUTED)); + return QVariant(item.value(AUDIOMODERATORMUTED).toBool()); case Role::VideoMuted: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::VIDEOMUTED)); + return QVariant(item.value(VIDEOMUTED).toBool()); case Role::IsModerator: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::ISMODERATOR)); + return QVariant(item.value(ISMODERATOR).toBool()); case Role::IsLocal: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::ISLOCAL)); + return QVariant(item.value(ISLOCAL).toBool()); case Role::IsContact: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::ISCONTACT)); + return QVariant(item.value(ISCONTACT).toBool()); case Role::Avatar: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::AVATAR)); + return QVariant(item.value(AVATAR).toString()); case Role::SinkId: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::STREAMID)); + return QVariant(item.value(STREAMID).toString()); case Role::Height: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::HEIGHT)); + return QVariant(item.value(HEIGHT).toInt()); case Role::Width: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::WIDTH)); + return QVariant(item.value(WIDTH).toInt()); case Role::HandRaised: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::HANDRAISED)); + return QVariant(item.value(HANDRAISED).toBool()); case Role::VoiceActivity: - return QVariant::fromValue( - participant.item.value(lrc::api::ParticipantsInfosStrings::VOICEACTIVITY)); + return QVariant(item.value(VOICEACTIVITY).toBool()); } return QVariant(); } @@ -124,11 +110,13 @@ CallParticipantsModel::addParticipant(int index, const QVariant& infos) return; beginInsertRows(QModelIndex(), index, index); - auto it = participants_.begin() + index; + const auto it = participants_.constBegin() + index; participants_.insert(it, CallParticipant::Item {infos.toJsonObject()}); endInsertRows(); + set_count(rowCount()); + callId_ = participants_[index].item[lrc::api::ParticipantsInfosStrings::CALLID].toString(); } @@ -157,10 +145,12 @@ CallParticipantsModel::removeParticipant(int index) beginRemoveRows(QModelIndex(), index, index); - auto it = participants_.begin() + index; + const auto it = participants_.constBegin() + index; participants_.erase(it); endRemoveRows(); + + set_count(rowCount()); } void @@ -176,12 +166,14 @@ CallParticipantsModel::setParticipants(const QString& callId, const QVariantList int idx = 0; for (const auto& participant : participants) { beginInsertRows(QModelIndex(), idx, idx); - auto it = participants_.begin() + idx; + const auto it = participants_.constBegin() + idx; participants_.insert(it, CallParticipant::Item {participant.toJsonObject()}); endInsertRows(); idx++; } } + + set_count(rowCount()); } void @@ -189,4 +181,6 @@ CallParticipantsModel::reset() { beginResetModel(); endResetModel(); + + set_count(rowCount()); } diff --git a/src/app/callparticipantsmodel.h b/src/app/callparticipantsmodel.h index bc485fb66a5e09785284ad0c218dc4024dba48cc..15330360c45830221c4af57eacb7da0874b4ee38 100644 --- a/src/app/callparticipantsmodel.h +++ b/src/app/callparticipantsmodel.h @@ -69,92 +69,13 @@ struct Item }; } // namespace CallParticipant -/* - * The CurrentAccountFilterModel class - * is for the sole purpose of filtering out current account. - */ -class GenericParticipantsFilterModel final : public QSortFilterProxyModel -{ - Q_OBJECT - QML_PROPERTY(bool, hideSelf) - QML_PROPERTY(bool, hideAudioOnly) - -public: - explicit GenericParticipantsFilterModel(LRCInstance* lrcInstance, - QAbstractListModel* parent = nullptr) - : QSortFilterProxyModel(parent) - , lrcInstance_(lrcInstance) - { - setSourceModel(parent); - setFilterRole(CallParticipant::Role::Active); - } - - virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override - { - // Accept all participants in participants list filtered with active status. - auto index = sourceModel()->index(sourceRow, 0, sourceParent); - - bool acceptState = !sourceModel()->data(index, CallParticipant::Role::Active).toBool(); - bool importantState = sourceModel()->data(index, CallParticipant::Role::HandRaised).toBool() - || sourceModel()->data(index, CallParticipant::Role::VoiceActivity).toBool(); - if (acceptState && !importantState - && ((hideSelf_ && sourceModel()->rowCount() > 1 && sourceModel()->data(index, CallParticipant::Role::IsLocal).toBool()) - || (hideAudioOnly_ && sourceModel()->rowCount() > 1 && sourceModel()->data(index, CallParticipant::Role::VideoMuted).toBool()))) - acceptState = false; - - return acceptState; - } - - Q_INVOKABLE void reset() - { - beginResetModel(); - endResetModel(); - } - -protected: - LRCInstance* lrcInstance_ {nullptr}; -}; - -/* - * The ActiveParticipantsFilterModel class - * is for the sole purpose of filtering out current account. - */ -class ActiveParticipantsFilterModel final : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - explicit ActiveParticipantsFilterModel(LRCInstance* lrcInstance, - QAbstractListModel* parent = nullptr) - : QSortFilterProxyModel(parent) - , lrcInstance_(lrcInstance) - { - setSourceModel(parent); - setFilterRole(CallParticipant::Role::Active); - } - - virtual bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override - { - // Accept all participants in participants list filtered with active status. - auto index = sourceModel()->index(sourceRow, 0, sourceParent); - return sourceModel()->data(index, CallParticipant::Role::Active).toBool(); - } - - Q_INVOKABLE void reset() - { - beginResetModel(); - endResetModel(); - } - -protected: - LRCInstance* lrcInstance_ {nullptr}; -}; - class CallParticipantsModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(LayoutType conferenceLayout READ conferenceLayout NOTIFY layoutChanged) + QML_RO_PROPERTY(int, count) + public: CallParticipantsModel(LRCInstance* instance, QObject* parent = nullptr); @@ -170,6 +91,7 @@ public: void removeParticipant(int index); void setParticipants(const QString& callId, const QVariantList& participants); Q_INVOKABLE void reset(); + void setConferenceLayout(int layout, const QString& callId) { auto newLayout = static_cast<LayoutType>(layout); @@ -178,13 +100,12 @@ public: Q_EMIT layoutChanged(); } } - const LayoutType conferenceLayout() + LayoutType conferenceLayout() { return layout_; } Q_SIGNALS: - void updateParticipant(QVariant participantInfos); void layoutChanged(); private: diff --git a/src/app/currentconversation.h b/src/app/currentconversation.h index 7ccbf511e63fa7d81c8f6b09b84a3aba4e4ab17f..37259baa4e56e12a75210e5f2478fc73f377f70c 100644 --- a/src/app/currentconversation.h +++ b/src/app/currentconversation.h @@ -51,6 +51,10 @@ class CurrentConversation final : public QObject QML_PROPERTY(QStringList, errors) QML_PROPERTY(QStringList, backendErrors) + // TODO: these belong in CurrentCall(which doesn't exist yet) + QML_PROPERTY(bool, hideSelf) + QML_PROPERTY(bool, hideAudioOnly) + public: explicit CurrentConversation(LRCInstance* lrcInstance, QObject* parent = nullptr); ~CurrentConversation() = default; diff --git a/src/app/mainview/components/CallActionBar.qml b/src/app/mainview/components/CallActionBar.qml index 159ca617bb4e0bbba42ad570463fd370f3d561be..f70faf8c9e1693e0fed5d52a122a8b309fc85bf3 100644 --- a/src/app/mainview/components/CallActionBar.qml +++ b/src/app/mainview/components/CallActionBar.qml @@ -178,15 +178,11 @@ Control { break case JamiStrings.hideSelf: UtilsAdapter.setAppValue(Settings.HideSelf, !layoutModel.get(index).ActiveSetting) - GenericParticipantsFilterModel.hideSelf = UtilsAdapter.getAppValue(Settings.HideSelf) - GenericParticipantsFilterModel.hideAudioOnly = UtilsAdapter.getAppValue(Settings.HideAudioOnly) - GenericParticipantsFilterModel.reset() + CurrentConversation.hideSelf = UtilsAdapter.getAppValue(Settings.HideSelf) break case JamiStrings.hideAudioOnly: UtilsAdapter.setAppValue(Settings.HideAudioOnly, !layoutModel.get(index).ActiveSetting) - GenericParticipantsFilterModel.hideSelf = UtilsAdapter.getAppValue(Settings.HideSelf) - GenericParticipantsFilterModel.hideAudioOnly = UtilsAdapter.getAppValue(Settings.HideAudioOnly) - GenericParticipantsFilterModel.reset() + CurrentConversation.hideAudioOnly = UtilsAdapter.getAppValue(Settings.HideAudioOnly) break } } diff --git a/src/app/mainview/components/ParticipantsLayer.qml b/src/app/mainview/components/ParticipantsLayer.qml index b4d28eef3791d431c352c305c1e2d8af674ab586..2e5c8b78c00d6a4c0abde7e032c5314f62a7fa67 100644 --- a/src/app/mainview/components/ParticipantsLayer.qml +++ b/src/app/mainview/components/ParticipantsLayer.qml @@ -22,6 +22,8 @@ import QtQuick import QtQuick.Layouts import QtQuick.Controls +import SortFilterProxyModel 0.2 + import net.jami.Adapters 1.1 import net.jami.Models 1.1 import net.jami.Constants 1.1 @@ -36,19 +38,8 @@ Item { property bool participantsSide onVisibleChanged: { - GenericParticipantsFilterModel.hideSelf = UtilsAdapter.getAppValue(Settings.HideSelf) - GenericParticipantsFilterModel.hideAudioOnly = UtilsAdapter.getAppValue(Settings.HideAudioOnly) - } - - Connections { - target: GenericParticipantsFilterModel - - function onHideSelfChanged() { - GenericParticipantsFilterModel.reset() - } - function onHideAudioOnlyChanged() { - GenericParticipantsFilterModel.reset() - } + CurrentConversation.hideSelf = UtilsAdapter.getAppValue(Settings.HideSelf) + CurrentConversation.hideAudioOnly = UtilsAdapter.getAppValue(Settings.HideAudioOnly) } Component { @@ -84,7 +75,7 @@ Item { enabled: bestName_ === uri_ function onRegisteredNameFound(status, address, name) { - if (address === uri_ && status == NameDirectory.LookupStatus.SUCCESS) { + if (address === uri_ && status === NameDirectory.LookupStatus.SUCCESS) { bestName_ = name } } @@ -92,11 +83,36 @@ Item { } } + SortFilterProxyModel { + id: genericParticipantsModel + sourceModel: CallParticipantsModel + filters: AllOf { + ValueFilter { roleName: "Active"; value: false } + ValueFilter { + enabled: CallParticipantsModel.count > 1 && + CurrentConversation.hideSelf + roleName: "IsLocal" + value: false + } + ValueFilter { + enabled: CallParticipantsModel.count > 1 && + CurrentConversation.hideAudioOnly + roleName: "VideoMuted" + value: false + } + } + } + + SortFilterProxyModel { + id: activeParticipantsModel + sourceModel: CallParticipantsModel + filters: ValueFilter { roleName: "Active"; value: true } + } + ParticipantsLayoutVertical { anchors.fill: parent participantComponent: callVideoMedia visible: !participantsSide - onLayoutCountChanged: root.count = layoutCount } diff --git a/src/app/mainview/components/ParticipantsLayoutHorizontal.qml b/src/app/mainview/components/ParticipantsLayoutHorizontal.qml index 8177921c8d55c311e92213472807cbfa697bc0d3..285be952817fb9da740184d2303de0ae0764ce2b 100644 --- a/src/app/mainview/components/ParticipantsLayoutHorizontal.qml +++ b/src/app/mainview/components/ParticipantsLayoutHorizontal.qml @@ -19,8 +19,8 @@ import QtQuick -import QtQuick.Layouts 1.15 -import QtQuick.Controls 2.15 +import QtQuick.Layouts +import QtQuick.Controls import net.jami.Adapters 1.1 import net.jami.Models 1.1 @@ -96,7 +96,7 @@ SplitView { anchors.fill: parent anchors.centerIn: parent - model: ActiveParticipantsFilterModel + model: activeParticipantsModel delegate: Loader { active: root.visible asynchronous: true @@ -262,7 +262,7 @@ SplitView { Repeater { id: commonParticipants - model: GenericParticipantsFilterModel + model: genericParticipantsModel delegate: Loader { sourceComponent: callVideoMedia active: root.visible diff --git a/src/app/mainview/components/ParticipantsLayoutVertical.qml b/src/app/mainview/components/ParticipantsLayoutVertical.qml index 9c1526068107ae410dac4f45d96670eb056b739a..23a834e17ff90a9384dd8ba25a2b562273aa9ccf 100644 --- a/src/app/mainview/components/ParticipantsLayoutVertical.qml +++ b/src/app/mainview/components/ParticipantsLayoutVertical.qml @@ -19,8 +19,8 @@ import QtQuick -import QtQuick.Layouts 1.15 -import QtQuick.Controls 2.15 +import QtQuick.Layouts +import QtQuick.Controls import net.jami.Adapters 1.1 import net.jami.Models 1.1 @@ -169,7 +169,7 @@ SplitView { Repeater { id: commonParticipants - model: GenericParticipantsFilterModel + model: genericParticipantsModel delegate: Loader { sourceComponent: callVideoMedia active: root.visible @@ -265,7 +265,7 @@ SplitView { anchors.fill: parent anchors.centerIn: parent - model: ActiveParticipantsFilterModel + model: activeParticipantsModel delegate: Loader { active: root.visible asynchronous: true