mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-08-07 16:26:12 +02:00
swarm: add call buttons and interactions for multi-swarm
+ Add call buttons to start a new call + React to events from the swarm + call interactions (Join call/Call ended, etc) + active calls area + Add call management logic in LRC + Feature is enabled via the experimental checkbox https://git.jami.net/savoirfairelinux/jami-daemon/-/issues/312 Change-Id: I83fd20b5e772097c0792bdc66feec69b0cb0009a
This commit is contained in:
parent
9b2dbb64ea
commit
0996b167d9
43 changed files with 1276 additions and 90 deletions
2
daemon
2
daemon
|
@ -1 +1 @@
|
|||
Subproject commit 54ffd0f4380bdbdc6a2fcb80847b2f5aefad4958
|
||||
Subproject commit 08ef8dd80d571816259b195b0956a472a40b58ad
|
|
@ -54,6 +54,7 @@ extern const QString defaultDownloadPath;
|
|||
X(NeverShowMeAgain, false) \
|
||||
X(WindowGeometry, QRectF(qQNaN(), qQNaN(), 0., 0.)) \
|
||||
X(WindowState, QWindow::AutomaticVisibility) \
|
||||
X(EnableExperimentalSwarm, false) \
|
||||
X(LANG, "SYSTEM")
|
||||
|
||||
/*
|
||||
|
|
|
@ -216,7 +216,8 @@ CallAdapter::onParticipantUpdated(const QString& callId, int index)
|
|||
return;
|
||||
}
|
||||
auto infos = getConferencesInfos();
|
||||
participantsModel_->updateParticipant(index, infos[index]);
|
||||
if (index < infos.size())
|
||||
participantsModel_->updateParticipant(index, infos[index]);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
@ -256,7 +257,8 @@ CallAdapter::onCallStatusChanged(const QString& callId, int code)
|
|||
const auto& currentConvInfo = lrcInstance_->getConversationFromConvUid(currentConvId);
|
||||
|
||||
// was it a conference and now is a dialog?
|
||||
if (currentConvInfo.confId.isEmpty() && currentConfSubcalls_.size() == 2) {
|
||||
if (currentConvInfo.isCoreDialog() && currentConvInfo.confId.isEmpty()
|
||||
&& currentConfSubcalls_.size() == 2) {
|
||||
auto it = std::find_if(currentConfSubcalls_.cbegin(),
|
||||
currentConfSubcalls_.cend(),
|
||||
[&callId](const QString& cid) { return cid != callId; });
|
||||
|
@ -495,14 +497,12 @@ CallAdapter::updateCall(const QString& convUid, const QString& accountId, bool f
|
|||
accountId_ = accountId.isEmpty() ? accountId_ : accountId;
|
||||
|
||||
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convUid);
|
||||
if (convInfo.uid.isEmpty()) {
|
||||
if (convInfo.uid.isEmpty())
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = lrcInstance_->getCallInfoForConversation(convInfo, forceCallOnly);
|
||||
if (!call) {
|
||||
if (!call)
|
||||
return;
|
||||
}
|
||||
|
||||
if (convInfo.uid == lrcInstance_->get_selectedConvUid()) {
|
||||
auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId_);
|
||||
|
|
118
src/app/commoncomponents/CallMessageDelegate.qml
Normal file
118
src/app/commoncomponents/CallMessageDelegate.qml
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Savoir-faire Linux Inc.
|
||||
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
SBSMessageBase {
|
||||
id: root
|
||||
|
||||
component JoinCallButton: PushButton {
|
||||
visible: root.isActive
|
||||
toolTipText: JamiStrings.joinCall
|
||||
preferredSize: 40
|
||||
imageColor: callLabel.color
|
||||
normalColor: "transparent"
|
||||
hoveredColor: Qt.rgba(255, 255, 255, 0.2)
|
||||
border.width: 1
|
||||
border.color: callLabel.color
|
||||
}
|
||||
|
||||
property bool isRemoteImage
|
||||
|
||||
isOutgoing: Author === ""
|
||||
author: Author
|
||||
readers: Readers
|
||||
formattedTime: MessagesAdapter.getFormattedTime(Timestamp)
|
||||
|
||||
|
||||
Connections {
|
||||
target: CurrentConversation
|
||||
enabled: root.isActive
|
||||
|
||||
function onActiveCallsChanged() {
|
||||
root.isActive = LRCInstance.indexOfActiveCall(ConfId, ActionUri, DeviceId) !== -1
|
||||
}
|
||||
}
|
||||
|
||||
property bool isActive: LRCInstance.indexOfActiveCall(ConfId, ActionUri, DeviceId) !== -1
|
||||
visible: isActive || ConfId === "" || Duration > 0
|
||||
|
||||
bubble.color: {
|
||||
if (ConfId === "" && Duration === 0) {
|
||||
// If missed, we can add a darker pattern
|
||||
return isOutgoing ?
|
||||
Qt.darker(JamiTheme.messageOutBgColor, 1.5) :
|
||||
Qt.darker(JamiTheme.messageInBgColor, 1.5)
|
||||
}
|
||||
return isOutgoing ?
|
||||
JamiTheme.messageOutBgColor :
|
||||
CurrentConversation.isCoreDialog ? JamiTheme.messageInBgColor : Qt.lighter(CurrentConversation.color, 1.5)
|
||||
}
|
||||
|
||||
innerContent.children: [
|
||||
RowLayout {
|
||||
id: msg
|
||||
anchors.right: isOutgoing ? parent.right : undefined
|
||||
spacing: 10
|
||||
visible: root.visible
|
||||
|
||||
Label {
|
||||
id: callLabel
|
||||
padding: 10
|
||||
Layout.margins: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
text:{
|
||||
if (root.isActive)
|
||||
return JamiStrings.joinCall
|
||||
return Body
|
||||
}
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
font.pointSize: JamiTheme.contactEventPointSize
|
||||
font.bold: true
|
||||
color: UtilsAdapter.luma(bubble.color) ?
|
||||
JamiTheme.chatviewTextColorLight :
|
||||
JamiTheme.chatviewTextColorDark
|
||||
}
|
||||
|
||||
JoinCallButton {
|
||||
id: joinCallInAudio
|
||||
|
||||
source: JamiResources.place_audiocall_24dp_svg
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, ConfId, true)
|
||||
}
|
||||
|
||||
JoinCallButton {
|
||||
id: joinCallInVideo
|
||||
|
||||
source: JamiResources.videocam_24dp_svg
|
||||
onClicked: MessagesAdapter.joinCall(ActionUri, DeviceId, ConfId)
|
||||
Layout.rightMargin: parent.spacing
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
opacity: 0
|
||||
Behavior on opacity { NumberAnimation { duration: 100 } }
|
||||
Component.onCompleted: opacity = 1
|
||||
}
|
|
@ -56,6 +56,7 @@ Control {
|
|||
readonly property real hPadding: JamiTheme.sbsMessageBasePreferredPadding
|
||||
width: ListView.view ? ListView.view.width : 0
|
||||
height: mainColumnLayout.implicitHeight
|
||||
|
||||
rightPadding: hPadding
|
||||
leftPadding: hPadding
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ SBSMessageBase {
|
|||
Math.min(implicitWidth, innerContent.width - senderMargin)
|
||||
}
|
||||
|
||||
height: implicitHeight
|
||||
wrapMode: Label.WrapAtWordBoundaryOrAnywhere
|
||||
selectByMouse: true
|
||||
font.pixelSize: isEmojiOnly? JamiTheme.chatviewEmojiSize : JamiTheme.chatviewFontSize
|
||||
|
|
|
@ -487,6 +487,9 @@ Item {
|
|||
property string troubleshootButton: qsTr("Open logs")
|
||||
property string troubleshootText: qsTr("Get logs")
|
||||
|
||||
property string experimentalCallSwarm: qsTr("(Experimental) Enable call support for swarm")
|
||||
property string experimentalCallSwarmTooltip: qsTr("This feature will enable call buttons in swarms with multiple participants.")
|
||||
|
||||
// Recording Settings
|
||||
property string tipRecordFolder: qsTr("Select a record directory")
|
||||
property string quality: qsTr("Quality")
|
||||
|
@ -727,6 +730,15 @@ Item {
|
|||
property string writeTo: qsTr("Write to %1")
|
||||
property string edit: qsTr("Edit")
|
||||
property string edited: qsTr("Edited")
|
||||
property string joinCall: qsTr("Join call")
|
||||
property string wantToJoin: qsTr("A call is in progress. Do you want to join the call?")
|
||||
property string needsHost: qsTr("Current host for this swarm seems unreachable. Do you want to host the call?")
|
||||
property string chooseHoster: qsTr("Choose a dedicated device for hosting future calls in this swarm. If not set, the device starting a call will host it.")
|
||||
property string chooseThisDevice: qsTr("Choose this device")
|
||||
property string removeCurrentDevice: qsTr("Remove current device")
|
||||
property string becomeHostOneCall: qsTr("Host only this call")
|
||||
property string hostThisCall: qsTr("Host this call")
|
||||
property string becomeDefaultHost: qsTr("Make me the default host for future calls")
|
||||
|
||||
// Invitation View
|
||||
property string invitationViewSentRequest: qsTr("%1 has sent you a request for a conversation.")
|
||||
|
@ -745,9 +757,11 @@ Item {
|
|||
property string muteConversation: qsTr("Mute conversation")
|
||||
property string ignoreNotificationsTooltip: qsTr("Ignore all notifications from this conversation")
|
||||
property string chooseAColor: qsTr("Choose a color")
|
||||
property string defaultCallHost: qsTr("Default host (calls)")
|
||||
property string leaveTheSwarm: qsTr("Leave the swarm")
|
||||
property string leave: qsTr("Leave")
|
||||
property string typeOfSwarm: qsTr("Type of swarm")
|
||||
property string none: qsTr("None")
|
||||
|
||||
// NewSwarmPage
|
||||
property string youCanAdd8: qsTr("You can add 8 people in the swarm")
|
||||
|
|
|
@ -94,6 +94,9 @@ ConversationListModelBase::dataForItem(item_t item, int role) const
|
|||
return lrcInstance_->getContentDraft(item.uid, item.accountId);
|
||||
return {};
|
||||
}
|
||||
case Role::ActiveCallsCount: {
|
||||
return item.activeCalls.size();
|
||||
}
|
||||
case Role::IsRequest:
|
||||
return QVariant(item.isRequest);
|
||||
case Role::Title:
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
X(CallState) \
|
||||
X(SectionName) \
|
||||
X(AccountId) \
|
||||
X(ActiveCallsCount) \
|
||||
X(Draft) \
|
||||
X(IsRequest) \
|
||||
X(Mode) \
|
||||
|
|
|
@ -59,7 +59,7 @@ ConversationsAdapter::ConversationsAdapter(SystemTray* systemTray,
|
|||
} else {
|
||||
// selected
|
||||
const auto& convInfo = lrcInstance_->getConversationFromConvUid(convId);
|
||||
if (convInfo.uid.isEmpty())
|
||||
if (convInfo.uid.isEmpty() || convInfo.accountId != lrcInstance_->get_currentAccountId())
|
||||
return;
|
||||
|
||||
auto& accInfo = lrcInstance_->getAccountInfo(convInfo.accountId);
|
||||
|
@ -528,6 +528,16 @@ ConversationsAdapter::popFrontError(const QString& convId)
|
|||
convModel->popFrontError(convId);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationsAdapter::ignoreActiveCall(const QString& convId,
|
||||
const QString& id,
|
||||
const QString& uri,
|
||||
const QString& device)
|
||||
{
|
||||
auto convModel = lrcInstance_->getCurrentConversationModel();
|
||||
convModel->ignoreActiveCall(convId, id, uri, device);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationsAdapter::updateConversationDescription(const QString& convId,
|
||||
const QString& newDescription)
|
||||
|
|
|
@ -59,6 +59,10 @@ public:
|
|||
Q_INVOKABLE void restartConversation(const QString& convId);
|
||||
Q_INVOKABLE void updateConversationTitle(const QString& convId, const QString& newTitle);
|
||||
Q_INVOKABLE void popFrontError(const QString& convId);
|
||||
Q_INVOKABLE void ignoreActiveCall(const QString& convId,
|
||||
const QString& id,
|
||||
const QString& uri,
|
||||
const QString& device);
|
||||
Q_INVOKABLE void updateConversationDescription(const QString& convId,
|
||||
const QString& newDescription);
|
||||
|
||||
|
|
|
@ -111,6 +111,7 @@ CurrentAccount::updateData()
|
|||
set_enabled(accInfo.enabled);
|
||||
set_managerUri(accConfig.managerUri);
|
||||
set_keepAliveEnabled(accConfig.keepAliveEnabled, true);
|
||||
set_deviceId(accConfig.deviceId);
|
||||
set_peerDiscovery(accConfig.peerDiscovery, true);
|
||||
set_sendReadReceipt(accConfig.sendReadReceipt, true);
|
||||
set_isRendezVous(accConfig.isRendezVous, true);
|
||||
|
|
|
@ -101,6 +101,7 @@ class CurrentAccount final : public QObject
|
|||
|
||||
QML_RO_PROPERTY(QString, id)
|
||||
QML_RO_PROPERTY(QString, uri)
|
||||
QML_RO_PROPERTY(QString, deviceId)
|
||||
QML_RO_PROPERTY(QString, registeredName)
|
||||
QML_RO_PROPERTY(QString, alias)
|
||||
QML_RO_PROPERTY(QString, bestId)
|
||||
|
|
|
@ -53,8 +53,6 @@ CurrentConversation::updateData()
|
|||
const auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
|
||||
if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
set_title(accInfo.conversationModel->title(convId));
|
||||
set_description(accInfo.conversationModel->description(convId));
|
||||
set_uris(convInfo.participantsUris());
|
||||
set_isSwarm(convInfo.isSwarm());
|
||||
set_isLegacy(convInfo.isLegacy());
|
||||
|
@ -104,6 +102,9 @@ CurrentConversation::updateData()
|
|||
} else if (convInfo.mode == conversation::Mode::PUBLIC) {
|
||||
set_modeString(tr("Public group"));
|
||||
}
|
||||
|
||||
onProfileUpdated(convId);
|
||||
updateActiveCalls(accountId, convId);
|
||||
}
|
||||
} catch (...) {
|
||||
qWarning() << "Can't update current conversation data for" << convId;
|
||||
|
@ -111,33 +112,58 @@ CurrentConversation::updateData()
|
|||
updateErrors(convId);
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::onNeedsHost(const QString& convId)
|
||||
{
|
||||
if (id_ != convId)
|
||||
return;
|
||||
Q_EMIT needsHost();
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::setPreference(const QString& key, const QString& value)
|
||||
{
|
||||
if (key == "color")
|
||||
set_color(value);
|
||||
auto preferences = getPreferences();
|
||||
preferences[key] = value;
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
const auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
|
||||
auto convId = lrcInstance_->get_selectedConvUid();
|
||||
if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
auto preferences = convInfo.preferences;
|
||||
preferences[key] = value;
|
||||
accInfo.conversationModel->setConversationPreferences(convId, preferences);
|
||||
}
|
||||
accInfo.conversationModel->setConversationPreferences(convId, preferences);
|
||||
}
|
||||
|
||||
QString
|
||||
CurrentConversation::getPreference(const QString& key) const
|
||||
{
|
||||
return getPreferences()[key];
|
||||
}
|
||||
|
||||
MapStringString
|
||||
CurrentConversation::getPreferences() const
|
||||
{
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
const auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
|
||||
auto convId = lrcInstance_->get_selectedConvUid();
|
||||
if (auto optConv = accInfo.conversationModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
return convInfo.preferences[key];
|
||||
auto preferences = accInfo.conversationModel->getConversationPreferences(convId);
|
||||
return preferences;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::setInfo(const QString& key, const QString& value)
|
||||
{
|
||||
MapStringString infos;
|
||||
infos[key] = value;
|
||||
auto accountId = lrcInstance_->get_currentAccountId();
|
||||
const auto& accInfo = lrcInstance_->accountModel().getAccountInfo(accountId);
|
||||
auto convId = lrcInstance_->get_selectedConvUid();
|
||||
accInfo.conversationModel->updateConversationInfos(convId, infos);
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::onConversationUpdated(const QString& convId)
|
||||
{
|
||||
|
@ -153,8 +179,27 @@ CurrentConversation::onProfileUpdated(const QString& convId)
|
|||
// filter for our currently set id
|
||||
if (id_ != convId)
|
||||
return;
|
||||
set_title(lrcInstance_->getCurrentConversationModel()->title(convId));
|
||||
set_description(lrcInstance_->getCurrentConversationModel()->description(convId));
|
||||
const auto& convModel = lrcInstance_->getCurrentConversationModel();
|
||||
set_title(convModel->title(convId));
|
||||
set_description(convModel->description(convId));
|
||||
|
||||
try {
|
||||
if (auto optConv = convModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
// Now, update call informations (rdvAccount/device)
|
||||
if (convInfo.infos.contains("rdvAccount")) {
|
||||
set_rdvAccount(convInfo.infos["rdvAccount"]);
|
||||
} else {
|
||||
set_rdvAccount("");
|
||||
}
|
||||
if (convInfo.infos.contains("rdvDevice")) {
|
||||
set_rdvDevice(convInfo.infos["rdvDevice"]);
|
||||
} else {
|
||||
set_rdvDevice("");
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -200,11 +245,21 @@ CurrentConversation::connectModel()
|
|||
this,
|
||||
&CurrentConversation::updateErrors,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::activeCallsChanged,
|
||||
this,
|
||||
&CurrentConversation::updateActiveCalls,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::conversationPreferencesUpdated,
|
||||
this,
|
||||
&CurrentConversation::updateConversationPreferences,
|
||||
Qt::UniqueConnection);
|
||||
connect(lrcInstance_->getCurrentConversationModel(),
|
||||
&ConversationModel::needsHost,
|
||||
this,
|
||||
&CurrentConversation::onNeedsHost,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -246,6 +301,42 @@ CurrentConversation::updateErrors(const QString& convId)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::updateActiveCalls(const QString&, const QString& convId)
|
||||
{
|
||||
if (convId != id_)
|
||||
return;
|
||||
const auto& convModel = lrcInstance_->getCurrentConversationModel();
|
||||
if (auto optConv = convModel->getConversationForUid(convId)) {
|
||||
auto& convInfo = optConv->get();
|
||||
QVariantList callList;
|
||||
for (int i = 0; i < convInfo.activeCalls.size(); i++) {
|
||||
// Check if ignored.
|
||||
auto ignored = false;
|
||||
for (int ignoredIdx = 0; ignoredIdx < convInfo.ignoredActiveCalls.size(); ignoredIdx++) {
|
||||
auto& ignoreCall = convInfo.ignoredActiveCalls[ignoredIdx];
|
||||
if (ignoreCall["id"] == convInfo.activeCalls[i]["id"]
|
||||
&& ignoreCall["uri"] == convInfo.activeCalls[i]["uri"]
|
||||
&& ignoreCall["device"] == convInfo.activeCalls[i]["device"]) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ignored) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Else, add to model
|
||||
QVariantMap mapCall;
|
||||
Q_FOREACH (QString key, convInfo.activeCalls[i].keys()) {
|
||||
mapCall[key] = convInfo.activeCalls[i][key];
|
||||
}
|
||||
callList.append(mapCall);
|
||||
}
|
||||
set_activeCalls(callList);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CurrentConversation::scrollToMsg(const QString& msg)
|
||||
{
|
||||
|
|
|
@ -43,12 +43,15 @@ class CurrentConversation final : public QObject
|
|||
QML_PROPERTY(bool, ignoreNotifications)
|
||||
QML_PROPERTY(QString, callId)
|
||||
QML_PROPERTY(QString, color)
|
||||
QML_PROPERTY(QString, rdvAccount)
|
||||
QML_PROPERTY(QString, rdvDevice)
|
||||
QML_PROPERTY(call::Status, callState)
|
||||
QML_PROPERTY(bool, inCall)
|
||||
QML_PROPERTY(bool, isTemporary)
|
||||
QML_PROPERTY(bool, isContact)
|
||||
QML_PROPERTY(bool, allMessagesLoaded)
|
||||
QML_PROPERTY(QString, modeString)
|
||||
QML_PROPERTY(QVariantList, activeCalls)
|
||||
QML_PROPERTY(QStringList, errors)
|
||||
QML_PROPERTY(QStringList, backendErrors)
|
||||
|
||||
|
@ -63,6 +66,8 @@ public:
|
|||
Q_INVOKABLE void showSwarmDetails() const;
|
||||
Q_INVOKABLE void setPreference(const QString& key, const QString& value);
|
||||
Q_INVOKABLE QString getPreference(const QString& key) const;
|
||||
Q_INVOKABLE MapStringString getPreferences() const;
|
||||
Q_INVOKABLE void setInfo(const QString& key, const QString& value);
|
||||
|
||||
Q_SIGNALS:
|
||||
void scrollTo(const QString& msgId);
|
||||
|
@ -70,10 +75,15 @@ Q_SIGNALS:
|
|||
|
||||
private Q_SLOTS:
|
||||
void updateData();
|
||||
void onNeedsHost(const QString& convId);
|
||||
void onConversationUpdated(const QString& convId);
|
||||
void onProfileUpdated(const QString& convId);
|
||||
void updateErrors(const QString& convId);
|
||||
void updateConversationPreferences(const QString& convId);
|
||||
void updateActiveCalls(const QString&, const QString& convId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void needsHost();
|
||||
|
||||
private:
|
||||
LRCInstance* lrcInstance_;
|
||||
|
|
|
@ -190,9 +190,8 @@ LRCInstance::getCallInfoForConversation(const conversation::Info& convInfo, bool
|
|||
auto callId = forceCallOnly
|
||||
? convInfo.callId
|
||||
: (convInfo.confId.isEmpty() ? convInfo.callId : convInfo.confId);
|
||||
if (!accInfo.callModel->hasCall(callId)) {
|
||||
if (!accInfo.callModel->hasCall(callId))
|
||||
return nullptr;
|
||||
}
|
||||
return &accInfo.callModel->getCall(callId);
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
|
@ -372,6 +371,16 @@ LRCInstance::selectConversation(const QString& convId, const QString& accountId)
|
|||
set_selectedConvUid(convId);
|
||||
}
|
||||
|
||||
int
|
||||
LRCInstance::indexOfActiveCall(const QString& confId, const QString& uri, const QString& deviceId)
|
||||
{
|
||||
if (auto optConv = getCurrentConversationModel()->getConversationForUid(selectedConvUid_)) {
|
||||
auto& convInfo = optConv->get();
|
||||
return convInfo.indexOfActiveCall({{"confId", confId}, {"uri", uri}, {"device", deviceId}});
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
LRCInstance::deselectConversation()
|
||||
{
|
||||
|
|
|
@ -108,6 +108,9 @@ public:
|
|||
Q_INVOKABLE void setContentDraft(const QString& convUid,
|
||||
const QString& accountId,
|
||||
const QString& content);
|
||||
Q_INVOKABLE int indexOfActiveCall(const QString& confId,
|
||||
const QString& uri,
|
||||
const QString& deviceId);
|
||||
|
||||
int getCurrentAccountIndex();
|
||||
void setCurrAccDisplayName(const QString& displayName);
|
||||
|
|
|
@ -47,6 +47,10 @@ Rectangle {
|
|||
|
||||
color: JamiTheme.chatviewBgColor
|
||||
|
||||
HostPopup {
|
||||
id: hostPopup
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: root
|
||||
|
||||
|
@ -88,6 +92,10 @@ Rectangle {
|
|||
addMemberPanel.visible = !addMemberPanel.visible
|
||||
}
|
||||
}
|
||||
|
||||
function onNeedsHost() {
|
||||
hostPopup.open()
|
||||
}
|
||||
}
|
||||
|
||||
onAddToConversationClicked: {
|
||||
|
@ -105,9 +113,38 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
Connections {
|
||||
target: CurrentConversation
|
||||
enabled: true
|
||||
|
||||
function onActiveCallsChanged() {
|
||||
if (CurrentConversation.activeCalls.length > 0) {
|
||||
notificationArea.id = CurrentConversation.activeCalls[0]["id"]
|
||||
notificationArea.uri = CurrentConversation.activeCalls[0]["uri"]
|
||||
notificationArea.device = CurrentConversation.activeCalls[0]["device"]
|
||||
}
|
||||
notificationArea.visible = CurrentConversation.activeCalls.length > 0
|
||||
}
|
||||
|
||||
function onErrorsChanged() {
|
||||
if (CurrentConversation.errors.length > 0) {
|
||||
errorRect.errorLabel.text = CurrentConversation.errors[0]
|
||||
errorRect.backendErrorToolTip.text = JamiStrings.backendError.arg(CurrentConversation.backendErrors[0])
|
||||
}
|
||||
errorRect.visible = CurrentConversation.errors.length > 0 // If too much noise: && LRCInstance.debugMode()
|
||||
}
|
||||
}
|
||||
|
||||
ConversationErrorsRow {
|
||||
id: errorRect
|
||||
color: JamiTheme.filterBadgeColor
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
|
||||
visible: false
|
||||
}
|
||||
|
||||
NotificationArea {
|
||||
id: notificationArea
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: JamiTheme.chatViewHeaderPreferredHeight
|
||||
visible: false
|
||||
|
|
|
@ -20,9 +20,10 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import net.jami.Models 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
|
@ -155,7 +156,7 @@ Rectangle {
|
|||
PushButton {
|
||||
id: startAAudioCallButton
|
||||
|
||||
visible: interactionButtonsVisibility && !addMemberVisibility
|
||||
visible: interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
|
||||
|
||||
source: JamiResources.place_audiocall_24dp_svg
|
||||
toolTipText: JamiStrings.placeAudioCall
|
||||
|
@ -169,7 +170,7 @@ Rectangle {
|
|||
PushButton {
|
||||
id: startAVideoCallButton
|
||||
|
||||
visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && !addMemberVisibility
|
||||
visible: CurrentAccount.videoEnabled_Video && interactionButtonsVisibility && (!addMemberVisibility || UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
|
||||
source: JamiResources.videocam_24dp_svg
|
||||
toolTipText: JamiStrings.placeVideoCall
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ Rectangle {
|
|||
errorRect.visible = CurrentConversation.errors.length > 0 && LRCInstance.debugMode()
|
||||
}
|
||||
}
|
||||
color: JamiTheme.filterBadgeColor
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
|
219
src/app/mainview/components/DevicesListPopup.qml
Normal file
219
src/app/mainview/components/DevicesListPopup.qml
Normal file
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright (C) 2020-2022 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
BaseModalDialog {
|
||||
id: root
|
||||
|
||||
width: 488
|
||||
height: 320
|
||||
|
||||
popupContent: Rectangle {
|
||||
id: rect
|
||||
|
||||
color: JamiTheme.transparentColor
|
||||
width: root.width
|
||||
|
||||
|
||||
PushButton {
|
||||
id: btnCancel
|
||||
imageColor: "grey"
|
||||
normalColor: "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
onClicked: { close(); }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: JamiTheme.preferredMarginSize
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
|
||||
Label {
|
||||
id: informativeLabel
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: JamiStrings.chooseHoster
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
}
|
||||
|
||||
JamiListView {
|
||||
id: devicesListView
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 160
|
||||
|
||||
model: SortFilterProxyModel {
|
||||
sourceModel: DeviceItemListModel
|
||||
sorters: [
|
||||
RoleSorter { roleName: "IsCurrent"; sortOrder: Qt.DescendingOrder },
|
||||
StringSorter {
|
||||
roleName: "DeviceName"
|
||||
caseSensitivity: Qt.CaseInsensitive
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
id: item
|
||||
|
||||
property string deviceName : DeviceName
|
||||
property string deviceId : DeviceID
|
||||
property bool isCurrent : DeviceName
|
||||
|
||||
implicitWidth: devicesListView.width
|
||||
width: devicesListView.width
|
||||
height: 70
|
||||
|
||||
highlighted: ListView.isCurrentItem
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
devicesListView.currentIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: highlighted? JamiTheme.selectedColor : JamiTheme.editBackgroundColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: item
|
||||
|
||||
Image {
|
||||
id: deviceImage
|
||||
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 24
|
||||
Layout.preferredHeight: 24
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
layer {
|
||||
enabled: true
|
||||
effect: ColorOverlay {
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
}
|
||||
source: JamiResources.baseline_desktop_windows_24dp_svg
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: deviceInfoColumnLayout
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
Text {
|
||||
id: labelDeviceName
|
||||
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
elide: Text.ElideRight
|
||||
color: JamiTheme.textColor
|
||||
text: deviceName
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelDeviceId
|
||||
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||
Layout.fillWidth: true
|
||||
|
||||
elide: Text.ElideRight
|
||||
color: JamiTheme.textColor
|
||||
text: deviceId === "" ? qsTr("Device Id") : deviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomBorder {
|
||||
commonBorder: false
|
||||
lBorderwidth: 0
|
||||
rBorderwidth: 0
|
||||
tBorderwidth: 0
|
||||
bBorderwidth: 2
|
||||
borderColor: JamiTheme.selectedColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: JamiTheme.preferredMarginSize
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
MaterialButton {
|
||||
id: chooseBtn
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
enabled: devicesListView.currentItem
|
||||
|
||||
text: JamiStrings.chooseThisDevice
|
||||
toolTipText: JamiStrings.chooseThisDevice
|
||||
|
||||
onClicked: {
|
||||
CurrentConversation.setInfo("rdvAccount", CurrentAccount.uri)
|
||||
CurrentConversation.setInfo("rdvDevice", devicesListView.currentItem.deviceId)
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: rmDeviceBtn
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
enabled: devicesListView.currentItem
|
||||
|
||||
text: JamiStrings.removeCurrentDevice
|
||||
toolTipText: JamiStrings.removeCurrentDevice
|
||||
|
||||
onClicked: {
|
||||
CurrentConversation.setInfo("rdvAccount", "")
|
||||
CurrentConversation.setInfo("rdvDevice", "")
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
111
src/app/mainview/components/HostPopup.qml
Normal file
111
src/app/mainview/components/HostPopup.qml
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
BaseModalDialog {
|
||||
id: root
|
||||
|
||||
width: 488
|
||||
height: 256
|
||||
|
||||
property bool isAdmin: {
|
||||
var role = UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, CurrentAccount.uri)
|
||||
return role === Member.Role.ADMIN
|
||||
}
|
||||
|
||||
|
||||
popupContent: Rectangle {
|
||||
id: rect
|
||||
|
||||
color: JamiTheme.transparentColor
|
||||
width: root.width
|
||||
|
||||
|
||||
PushButton {
|
||||
id: btnCancel
|
||||
imageColor: "grey"
|
||||
normalColor: "transparent"
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
anchors.rightMargin: 10
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
onClicked: { close();}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
anchors.fill: parent
|
||||
anchors.margins: JamiTheme.preferredMarginSize
|
||||
|
||||
Label {
|
||||
id: informativeLabel
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 26
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: JamiStrings.needsHost
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: becomeHostBtn
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
Layout.topMargin: 26
|
||||
text: isAdmin? JamiStrings.becomeHostOneCall : JamiStrings.hostThisCall
|
||||
|
||||
onClicked: {
|
||||
MessagesAdapter.joinCall(CurrentAccount.uri, CurrentAccount.deviceId, "0")
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
MaterialButton {
|
||||
id: becomeDefaultHostBtn
|
||||
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
text: JamiStrings.becomeDefaultHost
|
||||
toolTipText: JamiStrings.becomeDefaultHost
|
||||
|
||||
visible: isAdmin
|
||||
|
||||
onClicked: {
|
||||
CurrentConversation.setInfo("rdvAccount", CurrentAccount.uri)
|
||||
CurrentConversation.setInfo("rdvDevice", devicesListView.currentItem.deviceId)
|
||||
MessagesAdapter.joinCall(CurrentAccount.uri, CurrentAccount.deviceId, "0")
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -241,7 +241,7 @@ JamiListView {
|
|||
DelegateChoice {
|
||||
roleValue: Interaction.Type.CALL
|
||||
|
||||
GeneratedMessageDelegate {
|
||||
CallMessageDelegate {
|
||||
Component.onCompleted: {
|
||||
computeChatview(this, index)
|
||||
}
|
||||
|
|
117
src/app/mainview/components/NotificationArea.qml
Normal file
117
src/app/mainview/components/NotificationArea.qml
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Savoir-faire Linux Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
opacity: visible
|
||||
color: CurrentConversation.color
|
||||
|
||||
property string id: ""
|
||||
property string uri: ""
|
||||
property string device: ""
|
||||
|
||||
property string textColor: UtilsAdapter.luma(root.color) ?
|
||||
JamiTheme.chatviewTextColorLight :
|
||||
JamiTheme.chatviewTextColorDark
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: JamiTheme.preferredMarginSize
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
id: errorLabel
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.margins: 0
|
||||
text: JamiStrings.wantToJoin
|
||||
color: root.textColor
|
||||
font.pixelSize: JamiTheme.headerFontSize
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: controls
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
|
||||
PushButton {
|
||||
id: joinCallInAudio
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.rightMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
source: JamiResources.place_audiocall_24dp_svg
|
||||
toolTipText: JamiStrings.joinCall
|
||||
|
||||
imageColor: root.textColor
|
||||
normalColor: "transparent"
|
||||
hoveredColor: Qt.rgba(255, 255, 255, 0.2)
|
||||
border.width: 1
|
||||
border.color: root.textColor
|
||||
|
||||
onClicked: MessagesAdapter.joinCall(uri, device, id, true)
|
||||
}
|
||||
|
||||
|
||||
PushButton {
|
||||
id: joinCallInVideo
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.rightMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
source: JamiResources.videocam_24dp_svg
|
||||
toolTipText: JamiStrings.joinCall
|
||||
|
||||
imageColor: root.textColor
|
||||
normalColor: "transparent"
|
||||
hoveredColor: Qt.rgba(255, 255, 255, 0.2)
|
||||
border.width: 1
|
||||
border.color: root.textColor
|
||||
visible: CurrentAccount.videoEnabled_Video
|
||||
|
||||
onClicked: MessagesAdapter.joinCall(uri, device, id)
|
||||
}
|
||||
|
||||
PushButton {
|
||||
id: btnClose
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
|
||||
imageColor: root.textColor
|
||||
normalColor: JamiTheme.transparentColor
|
||||
|
||||
source: JamiResources.round_close_24dp_svg
|
||||
|
||||
onClicked: ConversationsAdapter.ignoreActiveCall(CurrentConversation.id, id, uri, device)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
from: 0
|
||||
duration: JamiTheme.shortFadeDuration
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,9 +22,10 @@ import QtQuick.Controls
|
|||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import net.jami.Models 1.1
|
||||
import net.jami.Adapters 1.1
|
||||
import net.jami.Constants 1.1
|
||||
import net.jami.Enums 1.1
|
||||
import net.jami.Models 1.1
|
||||
|
||||
import "../../commoncomponents"
|
||||
|
||||
|
@ -148,11 +149,6 @@ ItemDelegate {
|
|||
font.hintingPreference: Font.PreferNoHinting
|
||||
maximumLineCount: 1
|
||||
color: JamiTheme.textColor
|
||||
// deal with poor rendering of the pencil emoji on Windows
|
||||
font.family: Qt.platform.os === "windows" && Draft ?
|
||||
"Segoe UI Emoji" :
|
||||
Qt.application.font.family
|
||||
lineHeight: font.family === "Segoe UI Emoji" ? 1.25 : 1
|
||||
}
|
||||
}
|
||||
Text {
|
||||
|
@ -175,6 +171,13 @@ ItemDelegate {
|
|||
color: JamiTheme.primaryForegroundColor
|
||||
}
|
||||
|
||||
// Show that a call is ongoing for groups indicator
|
||||
ResponsiveImage {
|
||||
visible: ActiveCallsCount && !root.highlighted
|
||||
source: JamiResources.videocam_24dp_svg
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
spacing: 2
|
||||
|
@ -232,6 +235,8 @@ ItemDelegate {
|
|||
if (!interactive)
|
||||
return;
|
||||
ListView.view.model.select(index)
|
||||
if (CurrentConversation.isSwarm && !CurrentConversation.isCoreDialog && !UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm))
|
||||
return; // For now disable calls for swarm with multiple participants
|
||||
if (LRCInstance.currentAccountType === Profile.Type.SIP || !CurrentAccount.videoEnabled_Video)
|
||||
CallAdapter.placeAudioOnlyCall()
|
||||
else {
|
||||
|
|
|
@ -35,6 +35,11 @@ Rectangle {
|
|||
color: CurrentConversation.color
|
||||
property var isAdmin: UtilsAdapter.getParticipantRole(CurrentAccount.id, CurrentConversation.id, CurrentAccount.uri) === Member.Role.ADMIN
|
||||
|
||||
|
||||
DevicesListPopup {
|
||||
id: devicesListPopup
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: swarmProfileDetails
|
||||
Layout.fillHeight: true
|
||||
|
@ -343,6 +348,114 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
SwarmDetailsItem {
|
||||
id: settingsSwarmItem
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: JamiTheme.settingsFontSize + 2 * JamiTheme.preferredMarginSize + 4
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: JamiTheme.preferredMarginSize
|
||||
|
||||
Text {
|
||||
id: settingsSwarmText
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
Layout.rightMargin: JamiTheme.preferredMarginSize
|
||||
Layout.maximumWidth: settingsSwarmItem.width / 2
|
||||
|
||||
text: JamiStrings.defaultCallHost
|
||||
font.pointSize: JamiTheme.settingsFontSize
|
||||
font.kerning: true
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
color: JamiTheme.textColor
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: swarmRdvPref
|
||||
spacing: 10
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.maximumWidth: settingsSwarmItem.width / 2
|
||||
|
||||
Connections {
|
||||
target: CurrentConversation
|
||||
|
||||
function onRdvAccountChanged() {
|
||||
// This avoid incorrect avatar by always modifying the mode before the imageId
|
||||
avatar.mode = CurrentConversation.rdvAccount === CurrentAccount.uri ? Avatar.Mode.Account : Avatar.Mode.Contact
|
||||
avatar.imageId = CurrentConversation.rdvAccount === CurrentAccount.uri ? CurrentAccount.id : CurrentConversation.rdvAccount
|
||||
}
|
||||
}
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
width: JamiTheme.contactMessageAvatarSize
|
||||
height: JamiTheme.contactMessageAvatarSize
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
Layout.topMargin: JamiTheme.preferredMarginSize / 2
|
||||
visible: CurrentConversation.rdvAccount !== ""
|
||||
|
||||
imageId: ""
|
||||
showPresenceIndicator: false
|
||||
mode: Avatar.Mode.Account
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
ElidedTextLabel {
|
||||
id: bestName
|
||||
|
||||
eText: {
|
||||
if (CurrentConversation.rdvAccount === "")
|
||||
return JamiStrings.none
|
||||
else if (CurrentConversation.rdvAccount === CurrentAccount.uri)
|
||||
return CurrentAccount.bestName
|
||||
else
|
||||
return UtilsAdapter.getBestNameForUri(CurrentAccount.id, CurrentConversation.rdvAccount)
|
||||
}
|
||||
maxWidth: JamiTheme.preferredFieldWidth
|
||||
|
||||
font.pointSize: JamiTheme.participantFontSize
|
||||
color: JamiTheme.primaryForegroundColor
|
||||
font.kerning: true
|
||||
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
ElidedTextLabel {
|
||||
id: deviceId
|
||||
|
||||
eText: CurrentConversation.rdvDevice === "" ? JamiStrings.none : CurrentConversation.rdvDevice
|
||||
visible: CurrentConversation.rdvDevice !== ""
|
||||
maxWidth: JamiTheme.preferredFieldWidth
|
||||
|
||||
font.pointSize: JamiTheme.participantFontSize
|
||||
color: JamiTheme.textColorHovered
|
||||
font.kerning: true
|
||||
|
||||
horizontalAlignment: Text.AlignRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
target: parent
|
||||
|
||||
enabled: parent.visible && root.isAdmin
|
||||
onTapped: function onTapped(eventPoint) {
|
||||
devicesListPopup.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
Layout.preferredHeight: JamiTheme.settingsFontSize + 2 * JamiTheme.preferredMarginSize + 4
|
||||
|
|
|
@ -200,6 +200,19 @@ MessagesAdapter::retryInteraction(const QString& interactionId)
|
|||
->retryInteraction(lrcInstance_->get_selectedConvUid(), interactionId);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::joinCall(const QString& uri,
|
||||
const QString& deviceId,
|
||||
const QString& confId,
|
||||
bool isAudioOnly)
|
||||
{
|
||||
lrcInstance_->getCurrentConversationModel()->joinCall(lrcInstance_->get_selectedConvUid(),
|
||||
uri,
|
||||
deviceId,
|
||||
confId,
|
||||
isAudioOnly);
|
||||
}
|
||||
|
||||
void
|
||||
MessagesAdapter::copyToDownloads(const QString& interactionId, const QString& displayName)
|
||||
{
|
||||
|
|
|
@ -79,6 +79,10 @@ protected:
|
|||
Q_INVOKABLE void openDirectory(const QString& arg);
|
||||
Q_INVOKABLE void retryInteraction(const QString& interactionId);
|
||||
Q_INVOKABLE void deleteInteraction(const QString& interactionId);
|
||||
Q_INVOKABLE void joinCall(const QString& uri,
|
||||
const QString& deviceId,
|
||||
const QString& confId,
|
||||
bool isAudioOnly = false);
|
||||
Q_INVOKABLE void copyToDownloads(const QString& interactionId, const QString& displayName);
|
||||
Q_INVOKABLE void userIsComposing(bool isComposing);
|
||||
Q_INVOKABLE QVariantMap isLocalImage(const QString& mimeName);
|
||||
|
|
|
@ -85,4 +85,17 @@ ColumnLayout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
ToggleSwitch {
|
||||
id: checkboxCallSwarm
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: JamiTheme.preferredMarginSize
|
||||
checked: UtilsAdapter.getAppValue(Settings.EnableExperimentalSwarm)
|
||||
labelText: JamiStrings.experimentalCallSwarm
|
||||
fontPointSize: JamiTheme.settingsFontSize
|
||||
tooltipText: JamiStrings.experimentalCallSwarmTooltip
|
||||
onSwitchToggled: {
|
||||
UtilsAdapter.setAppValue(Settings.Key.EnableExperimentalSwarm, checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,6 +371,8 @@ UtilsAdapter::setAppValue(const Settings::Key key, const QVariant& value)
|
|||
settingsManager_->loadTranslations();
|
||||
else if (key == Settings::Key::BaseZoom)
|
||||
Q_EMIT changeFontSize();
|
||||
else if (key == Settings::Key::EnableExperimentalSwarm)
|
||||
Q_EMIT showExperimentalCallSwarm();
|
||||
else if (key == Settings::Key::ShowChatviewHorizontally)
|
||||
Q_EMIT chatviewPositionChanged();
|
||||
else if (key == Settings::Key::AppTheme)
|
||||
|
|
|
@ -126,6 +126,7 @@ Q_SIGNALS:
|
|||
void changeFontSize();
|
||||
void chatviewPositionChanged();
|
||||
void appThemeChanged();
|
||||
void showExperimentalCallSwarm();
|
||||
|
||||
private:
|
||||
QClipboard* clipboard_;
|
||||
|
|
|
@ -71,6 +71,9 @@ struct Info
|
|||
QString uid = "";
|
||||
QString accountId;
|
||||
QVector<member::Member> participants;
|
||||
VectorMapStringString activeCalls;
|
||||
VectorMapStringString ignoredActiveCalls;
|
||||
|
||||
QString callId;
|
||||
QString confId;
|
||||
std::unique_ptr<MessageListModel> interactions;
|
||||
|
@ -84,6 +87,18 @@ struct Info
|
|||
MapStringString infos {};
|
||||
MapStringString preferences {};
|
||||
|
||||
int indexOfActiveCall(const MapStringString& commit)
|
||||
{
|
||||
for (auto idx = 0; idx != activeCalls.size(); ++idx) {
|
||||
const auto& call = activeCalls[idx];
|
||||
if (call["id"] == commit["confId"] && call["uri"] == commit["uri"]
|
||||
&& call["device"] == commit["device"]) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString getCallId() const
|
||||
{
|
||||
return confId.isEmpty() ? callId : confId;
|
||||
|
|
|
@ -210,6 +210,11 @@ public:
|
|||
* @param uid of the conversation
|
||||
*/
|
||||
void placeAudioOnlyCall(const QString& uid);
|
||||
void joinCall(const QString& uid,
|
||||
const QString& confId,
|
||||
const QString& uri,
|
||||
const QString& deviceId,
|
||||
bool isAudioOnly);
|
||||
/**
|
||||
* Send a message to the conversation
|
||||
* @param uid of the conversation
|
||||
|
@ -378,6 +383,17 @@ public:
|
|||
* @param conversationId
|
||||
*/
|
||||
void popFrontError(const QString& conversationId);
|
||||
/**
|
||||
* Ignore an active call
|
||||
* @param convId
|
||||
* @param id
|
||||
* @param uri
|
||||
* @param device
|
||||
*/
|
||||
void ignoreActiveCall(const QString& convId,
|
||||
const QString& id,
|
||||
const QString& uri,
|
||||
const QString& device);
|
||||
|
||||
/**
|
||||
* @return if conversations requests exists.
|
||||
|
@ -432,15 +448,6 @@ Q_SIGNALS:
|
|||
void newInteraction(const QString& uid,
|
||||
QString& interactionId,
|
||||
const interaction::Info& interactionInfo) const;
|
||||
/**
|
||||
* Emitted when an interaction got a new status
|
||||
* @param convUid conversation which owns the interaction
|
||||
* @param interactionId
|
||||
* @param msg
|
||||
*/
|
||||
void interactionStatusUpdated(const QString& convUid,
|
||||
const QString& interactionId,
|
||||
const api::interaction::Info& msg) const;
|
||||
/**
|
||||
* Emitted when an interaction got removed from the conversation
|
||||
* @param convUid conversation which owns the interaction
|
||||
|
@ -546,6 +553,11 @@ Q_SIGNALS:
|
|||
*/
|
||||
void newMessagesAvailable(const QString& accountId, const QString& conversationId) const;
|
||||
|
||||
/**
|
||||
* Emitted whenever conversation's calls changed
|
||||
*/
|
||||
void activeCallsChanged(const QString& accountId, const QString& conversationId) const;
|
||||
|
||||
/**
|
||||
* Emitted when creation of conversation started, finished with success or finisfed with error
|
||||
* @param accountId account id
|
||||
|
@ -598,6 +610,11 @@ Q_SIGNALS:
|
|||
void messagesFoundProcessed(const QString& accountId,
|
||||
const VectorMapStringString& messageIds,
|
||||
const QVector<interaction::Info>& messageInformations) const;
|
||||
/**
|
||||
* Emitted once a conversation needs somebody to host the call
|
||||
* @param callId
|
||||
*/
|
||||
void needsHost(const QString& conversationId) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ConversationModelPimpl> pimpl_;
|
||||
|
|
|
@ -272,6 +272,7 @@ struct Info
|
|||
QString authorUri;
|
||||
QString body;
|
||||
QString parentId = "";
|
||||
QString confId;
|
||||
std::time_t timestamp = 0;
|
||||
std::time_t duration = 0;
|
||||
Type type = Type::INVALID;
|
||||
|
@ -318,6 +319,8 @@ struct Info
|
|||
body = QObject::tr("Swarm created");
|
||||
} else if (type == Type::CALL) {
|
||||
duration = message["duration"].toInt() / 1000;
|
||||
if (message.contains("confId"))
|
||||
confId = message["confId"];
|
||||
}
|
||||
commit = message;
|
||||
}
|
||||
|
|
|
@ -167,7 +167,7 @@ getFormattedCallDuration(const std::time_t duration)
|
|||
}
|
||||
|
||||
QString
|
||||
getCallInteractionString(const QString& authorUri, const std::time_t& duration)
|
||||
getCallInteractionStringNonSwarm(const QString& authorUri, const std::time_t& duration)
|
||||
{
|
||||
if (duration < 0) {
|
||||
if (authorUri.isEmpty()) {
|
||||
|
@ -190,6 +190,17 @@ getCallInteractionString(const QString& authorUri, const std::time_t& duration)
|
|||
}
|
||||
}
|
||||
|
||||
QString
|
||||
getCallInteractionString(const api::interaction::Info& info)
|
||||
{
|
||||
if (!info.confId.isEmpty()) {
|
||||
if (info.duration <= 0) {
|
||||
return QObject::tr("Join call");
|
||||
}
|
||||
}
|
||||
return getCallInteractionStringNonSwarm(info.authorUri, info.duration);
|
||||
}
|
||||
|
||||
QString
|
||||
getContactInteractionString(const QString& authorUri, const api::interaction::Status& status)
|
||||
{
|
||||
|
@ -510,7 +521,7 @@ getHistory(Database& db, api::conversation::Info& conversation)
|
|||
: std::stoi(durationString.toStdString());
|
||||
auto status = api::interaction::to_status(payloads[i + 5]);
|
||||
if (type == api::interaction::Type::CALL) {
|
||||
body = getCallInteractionString(payloads[i + 1], duration);
|
||||
body = getCallInteractionStringNonSwarm(payloads[i + 1], duration);
|
||||
} else if (type == api::interaction::Type::CONTACT) {
|
||||
body = getContactInteractionString(payloads[i + 1], status);
|
||||
}
|
||||
|
|
|
@ -56,11 +56,11 @@ QString prepareUri(const QString& uri, api::profile::Type type);
|
|||
|
||||
/**
|
||||
* Get a formatted string for a call interaction's body
|
||||
* @param author_uri
|
||||
* @param duration of the call
|
||||
* @param info
|
||||
* @return the formatted and translated call message string
|
||||
*/
|
||||
QString getCallInteractionString(const QString& authorUri, const std::time_t& duration);
|
||||
QString getCallInteractionString(const api::interaction::Info& info);
|
||||
QString getCallInteractionStringNonSwarm(const QString& authorUri, const std::time_t& duration);
|
||||
|
||||
/**
|
||||
* Get a formatted string for a contact interaction's body
|
||||
|
|
|
@ -128,6 +128,12 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
|
|||
&CallbacksHandler::slotAccountMessageStatusChanged,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::needsHost,
|
||||
this,
|
||||
&CallbacksHandler::slotNeedsHost,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::accountDetailsChanged,
|
||||
this,
|
||||
|
@ -349,6 +355,11 @@ CallbacksHandler::CallbacksHandler(const Lrc& parent)
|
|||
this,
|
||||
&CallbacksHandler::slotOnConversationError,
|
||||
Qt::QueuedConnection);
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::activeCallsChanged,
|
||||
this,
|
||||
&CallbacksHandler::slotActiveCallsChanged,
|
||||
Qt::QueuedConnection);
|
||||
connect(&ConfigurationManager::instance(),
|
||||
&ConfigurationManagerInterface::conversationPreferencesUpdated,
|
||||
this,
|
||||
|
@ -576,6 +587,7 @@ CallbacksHandler::slotConferenceChanged(const QString& accountId,
|
|||
const QString& callId,
|
||||
const QString& state)
|
||||
{
|
||||
Q_EMIT conferenceChanged(accountId, callId, state);
|
||||
slotCallStateChanged(accountId, callId, state, 0);
|
||||
}
|
||||
|
||||
|
@ -595,6 +607,12 @@ CallbacksHandler::slotAccountMessageStatusChanged(const QString& accountId,
|
|||
Q_EMIT accountMessageStatusChanged(accountId, conversationId, peer, messageId, status);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotNeedsHost(const QString& accountId, const QString& conversationId)
|
||||
{
|
||||
Q_EMIT needsHost(accountId, conversationId);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotDataTransferEvent(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
|
@ -823,6 +841,14 @@ CallbacksHandler::slotOnConversationError(const QString& accountId,
|
|||
Q_EMIT conversationError(accountId, conversationId, code, what);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotActiveCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls)
|
||||
{
|
||||
Q_EMIT activeCallsChanged(accountId, conversationId, activeCalls);
|
||||
}
|
||||
|
||||
void
|
||||
CallbacksHandler::slotConversationPreferencesUpdated(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
|
|
|
@ -195,12 +195,19 @@ Q_SIGNALS:
|
|||
* @param callId of the conference
|
||||
*/
|
||||
void conferenceCreated(const QString& accountId, const QString& callId);
|
||||
void conferenceChanged(const QString& accountId, const QString& confId, const QString& state);
|
||||
/**
|
||||
* Connect this signal to know when a conference is removed
|
||||
* @param accountId
|
||||
* @param callId of the conference
|
||||
*/
|
||||
void conferenceRemoved(const QString& accountId, const QString& callId);
|
||||
/**
|
||||
* Connect this signal to know if a conversation needs an host.
|
||||
* @param accountId, account linked
|
||||
* @param conversationId id of the conversation
|
||||
*/
|
||||
void needsHost(const QString& accountId, const QString& conversationId);
|
||||
/**
|
||||
* Connect this signal to know when a message sent get a new status
|
||||
* @param accountId, account linked
|
||||
|
@ -374,6 +381,9 @@ Q_SIGNALS:
|
|||
const QString& conversationId,
|
||||
int code,
|
||||
const QString& what);
|
||||
void activeCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls);
|
||||
void conversationPreferencesUpdated(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const MapStringString& preferences);
|
||||
|
@ -549,6 +559,12 @@ private Q_SLOTS:
|
|||
const QString& peer,
|
||||
const QString& messageId,
|
||||
int status);
|
||||
/**
|
||||
* Emit needsHost
|
||||
* @param accountId, account linked
|
||||
* @param conversationId id of the conversation
|
||||
*/
|
||||
void slotNeedsHost(const QString& accountId, const QString& conversationId);
|
||||
|
||||
void slotDataTransferEvent(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
|
@ -699,6 +715,9 @@ private Q_SLOTS:
|
|||
const QString& conversationId,
|
||||
int code,
|
||||
const QString& what);
|
||||
void slotActiveCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls);
|
||||
|
||||
private:
|
||||
const api::Lrc& parent;
|
||||
|
|
|
@ -239,6 +239,9 @@ public Q_SLOTS:
|
|||
* @param callId
|
||||
*/
|
||||
void slotConferenceCreated(const QString& accountId, const QString& callId);
|
||||
void slotConferenceChanged(const QString& accountId,
|
||||
const QString& callId,
|
||||
const QString& state);
|
||||
/**
|
||||
* Listen from CallbacksHandler when a voice mail notice is incoming
|
||||
* @param accountId
|
||||
|
@ -409,6 +412,23 @@ CallModel::getAdvancedInformation() const
|
|||
return pimpl_->callAdvancedInformation();
|
||||
}
|
||||
|
||||
void
|
||||
CallModel::emplaceConversationConference(const QString& confId)
|
||||
{
|
||||
if (hasCall(confId))
|
||||
return;
|
||||
|
||||
auto callInfo = std::make_shared<call::Info>();
|
||||
callInfo->id = confId;
|
||||
callInfo->isOutgoing = false;
|
||||
callInfo->status = call::Status::SEARCHING;
|
||||
callInfo->type = call::Type::CONFERENCE;
|
||||
callInfo->isAudioOnly = false;
|
||||
callInfo->videoMuted = false;
|
||||
callInfo->mediaList = {};
|
||||
pimpl_->calls.emplace(confId, std::move(callInfo));
|
||||
}
|
||||
|
||||
void
|
||||
CallModel::muteMedia(const QString& callId, const QString& label, bool mute)
|
||||
{
|
||||
|
@ -940,6 +960,10 @@ CallModelPimpl::CallModelPimpl(const CallModel& linked,
|
|||
&CallbacksHandler::conferenceCreated,
|
||||
this,
|
||||
&CallModelPimpl::slotConferenceCreated);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::conferenceChanged,
|
||||
this,
|
||||
&CallModelPimpl::slotConferenceChanged);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::voiceMailNotify,
|
||||
this,
|
||||
|
@ -1566,9 +1590,13 @@ CallModelPimpl::slotOnConferenceInfosUpdated(const QString& confId,
|
|||
QStringList callList = CallManager::instance().getParticipantList(linked.owner.id, confId);
|
||||
Q_FOREACH (const auto& call, callList) {
|
||||
Q_EMIT linked.callAddedToConference(call, confId);
|
||||
calls[call]->videoMuted = it->second->videoMuted;
|
||||
calls[call]->audioMuted = it->second->audioMuted;
|
||||
Q_EMIT linked.callInfosChanged(linked.owner.id, call);
|
||||
if (calls.find(call) == calls.end()) {
|
||||
qWarning() << "Call not found";
|
||||
} else {
|
||||
calls[call]->videoMuted = it->second->videoMuted;
|
||||
calls[call]->audioMuted = it->second->audioMuted;
|
||||
Q_EMIT linked.callInfosChanged(linked.owner.id, call);
|
||||
}
|
||||
}
|
||||
Q_EMIT linked.callInfosChanged(linked.owner.id, confId);
|
||||
Q_EMIT linked.onParticipantsChanged(confId);
|
||||
|
@ -1585,14 +1613,7 @@ CallModelPimpl::slotConferenceCreated(const QString& accountId, const QString& c
|
|||
{
|
||||
if (accountId != linked.owner.id)
|
||||
return;
|
||||
// Detect if conference is created for this account
|
||||
QStringList callList = CallManager::instance().getParticipantList(linked.owner.id, confId);
|
||||
auto hasConference = false;
|
||||
Q_FOREACH (const auto& call, callList) {
|
||||
hasConference |= linked.hasCall(call);
|
||||
}
|
||||
if (!hasConference)
|
||||
return;
|
||||
|
||||
auto callInfo = std::make_shared<call::Info>();
|
||||
callInfo->id = confId;
|
||||
|
@ -1625,6 +1646,20 @@ CallModelPimpl::slotConferenceCreated(const QString& accountId, const QString& c
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallModelPimpl::slotConferenceChanged(const QString& accountId,
|
||||
const QString& confId,
|
||||
const QString& state)
|
||||
{
|
||||
if (accountId != linked.owner.id)
|
||||
return;
|
||||
// Detect if conference is created for this account
|
||||
QStringList callList = CallManager::instance().getParticipantList(linked.owner.id, confId);
|
||||
Q_FOREACH (const auto& call, callList) {
|
||||
Q_EMIT linked.callAddedToConference(call, confId);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CallModelPimpl::sendProfile(const QString& callId)
|
||||
{
|
||||
|
|
|
@ -193,10 +193,7 @@ public:
|
|||
/**
|
||||
* Handle data transfer progression
|
||||
*/
|
||||
void updateTransferProgress(QTimer* timer,
|
||||
const QString& conversation,
|
||||
int conversationIdx,
|
||||
const QString& interactionId);
|
||||
void updateTransferProgress(QTimer* timer, int conversationIdx, const QString& interactionId);
|
||||
|
||||
bool usefulDataFromDataTransfer(const QString& fileId,
|
||||
const datatransfer::Info& info,
|
||||
|
@ -385,6 +382,9 @@ public Q_SLOTS:
|
|||
const QString& conversationId,
|
||||
int code,
|
||||
const QString& what);
|
||||
void slotActiveCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls);
|
||||
void slotConversationReady(const QString& accountId, const QString& conversationId);
|
||||
void slotConversationRemoved(const QString& accountId, const QString& conversationId);
|
||||
void slotConversationPreferencesUpdated(const QString& accountId,
|
||||
|
@ -853,6 +853,30 @@ ConversationModel::deleteObsoleteHistory(int days)
|
|||
storage::deleteObsoleteHistory(pimpl_->db, date);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModel::joinCall(const QString& uid,
|
||||
const QString& uri,
|
||||
const QString& deviceId,
|
||||
const QString& confId,
|
||||
bool isAudioOnly)
|
||||
{
|
||||
try {
|
||||
auto& conversation = pimpl_->getConversationForUid(uid, true).get();
|
||||
if (!conversation.callId.isEmpty()) {
|
||||
qWarning() << "Already in a call for swarm:" + uid;
|
||||
return;
|
||||
}
|
||||
conversation.callId = owner.callModel->createCall("rdv:" + uid + "/" + uri + "/" + deviceId
|
||||
+ "/" + confId,
|
||||
isAudioOnly);
|
||||
// Update interaction status
|
||||
pimpl_->invalidateModel();
|
||||
emit selectConversation(uid);
|
||||
emit conversationUpdated(uid);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly)
|
||||
{
|
||||
|
@ -864,6 +888,19 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly)
|
|||
<< "ConversationModel::placeCall can't call a conversation without participant";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!conversation.isCoreDialog() && conversation.isSwarm()) {
|
||||
qDebug() << "Start call for swarm:" + uid;
|
||||
conversation.callId = linked.owner.callModel->createCall("swarm:" + uid, isAudioOnly);
|
||||
|
||||
// Update interaction status
|
||||
invalidateModel();
|
||||
emit linked.selectConversation(conversation.uid);
|
||||
emit linked.conversationUpdated(conversation.uid);
|
||||
Q_EMIT linked.dataChanged(indexOf(conversation.uid));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& peers = peersForConversation(conversation);
|
||||
// there is no calls in group with more than 2 participants
|
||||
if (peers.size() != 1) {
|
||||
|
@ -1028,6 +1065,25 @@ ConversationModel::popFrontError(const QString& conversationId)
|
|||
Q_EMIT onConversationErrorsUpdated(conversationId);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModel::ignoreActiveCall(const QString& conversationId,
|
||||
const QString& id,
|
||||
const QString& uri,
|
||||
const QString& device)
|
||||
{
|
||||
auto conversationOpt = getConversationForUid(conversationId);
|
||||
if (!conversationOpt.has_value())
|
||||
return;
|
||||
|
||||
auto& conversation = conversationOpt->get();
|
||||
MapStringString mapCall;
|
||||
mapCall["id"] = id;
|
||||
mapCall["uri"] = uri;
|
||||
mapCall["device"] = device;
|
||||
conversation.ignoredActiveCalls.push_back(mapCall);
|
||||
Q_EMIT activeCallsChanged(owner.id, conversationId);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModel::setConversationPreferences(const QString& conversationId,
|
||||
const MapStringString prefs)
|
||||
|
@ -1835,6 +1891,9 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
|
|||
&ConfigurationManagerInterface::composingStatusChanged,
|
||||
this,
|
||||
&ConversationModelPimpl::slotComposingStatusChanged);
|
||||
connect(&callbacksHandler, &CallbacksHandler::needsHost, this, [&](auto, auto convId) {
|
||||
emit linked.needsHost(convId);
|
||||
});
|
||||
|
||||
// data transfer
|
||||
connect(&*linked.owner.contactModel,
|
||||
|
@ -1918,6 +1977,10 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked,
|
|||
&CallbacksHandler::conversationPreferencesUpdated,
|
||||
this,
|
||||
&ConversationModelPimpl::slotConversationPreferencesUpdated);
|
||||
connect(&callbacksHandler,
|
||||
&CallbacksHandler::activeCallsChanged,
|
||||
this,
|
||||
&ConversationModelPimpl::slotActiveCallsChanged);
|
||||
}
|
||||
|
||||
ConversationModelPimpl::~ConversationModelPimpl()
|
||||
|
@ -2066,6 +2129,10 @@ ConversationModelPimpl::~ConversationModelPimpl()
|
|||
&CallbacksHandler::conversationError,
|
||||
this,
|
||||
&ConversationModelPimpl::slotOnConversationError);
|
||||
disconnect(&callbacksHandler,
|
||||
&CallbacksHandler::activeCallsChanged,
|
||||
this,
|
||||
&ConversationModelPimpl::slotActiveCallsChanged);
|
||||
disconnect(&callbacksHandler,
|
||||
&CallbacksHandler::conversationPreferencesUpdated,
|
||||
this,
|
||||
|
@ -2383,7 +2450,7 @@ ConversationModelPimpl::slotConversationLoaded(uint32_t requestId,
|
|||
linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
|
||||
downloadFile = (bytesProgress == 0);
|
||||
} else if (msg.type == interaction::Type::CALL) {
|
||||
msg.body = storage::getCallInteractionString(msg.authorUri, msg.duration);
|
||||
msg.body = storage::getCallInteractionString(msg);
|
||||
} else if (msg.type == interaction::Type::CONTACT) {
|
||||
auto bestName = msg.authorUri == linked.owner.profileInfo.uri
|
||||
? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
|
||||
|
@ -2498,6 +2565,8 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
|
|||
api::datatransfer::Info info;
|
||||
QString fileId;
|
||||
|
||||
auto updateUnread = false;
|
||||
|
||||
if (msg.type == interaction::Type::DATA_TRANSFER) {
|
||||
// save data transfer interaction to db and assosiate daemon id with interaction id,
|
||||
// conversation id and db id
|
||||
|
@ -2520,8 +2589,14 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
|
|||
: bytesProgress == totalSize ? interaction::Status::TRANSFER_FINISHED
|
||||
: interaction::Status::TRANSFER_ONGOING;
|
||||
linked.owner.dataTransferModel->registerTransferId(fileId, msgId);
|
||||
if (msg.authorUri != linked.owner.profileInfo.uri) {
|
||||
updateUnread = true;
|
||||
}
|
||||
} else if (msg.type == interaction::Type::CALL) {
|
||||
msg.body = storage::getCallInteractionString(msg.authorUri, msg.duration);
|
||||
// If we're a call in a swarm
|
||||
if (msg.authorUri != linked.owner.profileInfo.uri)
|
||||
updateUnread = true;
|
||||
msg.body = storage::getCallInteractionString(msg);
|
||||
} else if (msg.type == interaction::Type::CONTACT) {
|
||||
auto bestName = msg.authorUri == linked.owner.profileInfo.uri
|
||||
? linked.owner.accountModel->bestNameForAccount(linked.owner.id)
|
||||
|
@ -2529,16 +2604,24 @@ ConversationModelPimpl::slotMessageReceived(const QString& accountId,
|
|||
msg.body = interaction::getContactInteractionString(bestName,
|
||||
interaction::to_action(
|
||||
message["action"]));
|
||||
} else if (msg.type == interaction::Type::TEXT
|
||||
&& msg.authorUri != linked.owner.profileInfo.uri) {
|
||||
conversation.unreadMessages++;
|
||||
if (msg.authorUri != linked.owner.profileInfo.uri) {
|
||||
updateUnread = true;
|
||||
}
|
||||
} else if (msg.type == interaction::Type::TEXT) {
|
||||
if (msg.authorUri != linked.owner.profileInfo.uri) {
|
||||
updateUnread = true;
|
||||
}
|
||||
} else if (msg.type == interaction::Type::EDITED) {
|
||||
conversation.interactions->addEdition(msgId, msg, true);
|
||||
}
|
||||
|
||||
if (!insertSwarmInteraction(msgId, msg, conversation, false)) {
|
||||
// message already exists
|
||||
return;
|
||||
}
|
||||
if (updateUnread) {
|
||||
conversation.unreadMessages++;
|
||||
}
|
||||
if (msg.type == interaction::Type::MERGE) {
|
||||
invalidateModel();
|
||||
return;
|
||||
|
@ -2811,6 +2894,24 @@ ConversationModelPimpl::slotOnConversationError(const QString& accountId,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModelPimpl::slotActiveCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls)
|
||||
{
|
||||
if (accountId != linked.owner.id || indexOf(conversationId) < 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto& conversation = getConversationForUid(conversationId).get();
|
||||
conversation.activeCalls = activeCalls;
|
||||
if (activeCalls.empty())
|
||||
conversation.ignoredActiveCalls.clear();
|
||||
Q_EMIT linked.activeCallsChanged(accountId, conversationId);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ConversationModelPimpl::slotIncomingContactRequest(const QString& contactUri)
|
||||
{
|
||||
|
@ -3074,6 +3175,9 @@ ConversationModelPimpl::addSwarmConversation(const QString& convId)
|
|||
conversation.infos = details;
|
||||
conversation.uid = convId;
|
||||
conversation.accountId = linked.owner.id;
|
||||
VectorMapStringString activeCalls = ConfigurationManager::instance()
|
||||
.getActiveCalls(linked.owner.id, convId);
|
||||
conversation.activeCalls = activeCalls;
|
||||
QString lastRead;
|
||||
VectorString membersLeft;
|
||||
for (auto& member : members) {
|
||||
|
@ -3387,12 +3491,13 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
|
|||
bool incoming,
|
||||
const std::time_t& duration)
|
||||
{
|
||||
// do not save call interaction for swarm conversation
|
||||
try {
|
||||
auto& conv = getConversationForPeerUri(from).get();
|
||||
if (conv.isSwarm())
|
||||
return;
|
||||
} catch (const std::exception&) {
|
||||
// Get conversation
|
||||
auto conv_it = std::find_if(conversations.begin(),
|
||||
conversations.end(),
|
||||
[&callId](const conversation::Info& conversation) {
|
||||
return conversation.callId == callId;
|
||||
});
|
||||
if (conv_it == conversations.end()) {
|
||||
// If we have no conversation with peer.
|
||||
try {
|
||||
auto contact = linked.owner.contactModel->getContact(from);
|
||||
|
@ -3401,18 +3506,18 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
|
|||
storage::beginConversationWithPeer(db, contact.profileInfo.uri);
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto& conv = getConversationForPeerUri(from).get();
|
||||
conv.callId = callId;
|
||||
} catch (...) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get conversation
|
||||
auto conv_it = std::find_if(conversations.begin(),
|
||||
conversations.end(),
|
||||
[&callId](const conversation::Info& conversation) {
|
||||
return conversation.callId == callId;
|
||||
});
|
||||
if (conv_it == conversations.end()) {
|
||||
// do not save call interaction for swarm conversation
|
||||
if (conv_it->isSwarm())
|
||||
return;
|
||||
}
|
||||
auto uid = conv_it->uid;
|
||||
auto uriString = incoming ? storage::prepareUri(from, linked.owner.profileInfo.type) : "";
|
||||
auto msg = interaction::Info {uriString,
|
||||
|
@ -3425,7 +3530,7 @@ ConversationModelPimpl::addOrUpdateCallMessage(const QString& callId,
|
|||
// update the db
|
||||
auto msgId = storage::addOrUpdateMessage(db, conv_it->uid, msg, callId);
|
||||
// now set the formatted call message string in memory only
|
||||
msg.body = storage::getCallInteractionString(uriString, duration);
|
||||
msg.body = storage::getCallInteractionString(msg);
|
||||
bool newInteraction = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(interactionsLocks[conv_it->uid]);
|
||||
|
@ -3562,6 +3667,7 @@ ConversationModelPimpl::slotCallAddedToConference(const QString& callId, const Q
|
|||
.getConferenceDetails(linked.owner.id, confId);
|
||||
if (confDetails["STATE"] == "ACTIVE_ATTACHED")
|
||||
Q_EMIT linked.selectConversation(conversation.uid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3693,7 +3799,7 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const QString& accountId,
|
|||
if (peerId != linked.owner.profileInfo.uri)
|
||||
conversation.interactions->setRead(peerId, messageId);
|
||||
else {
|
||||
// Here, this means that the daemon synched the displayed message
|
||||
// Here, this means that the daemon synced the displayed message
|
||||
// so, compute the number of unread messages.
|
||||
conversation.unreadMessages = ConfigurationManager::instance()
|
||||
.countInteractions(linked.owner.id,
|
||||
|
@ -4150,7 +4256,7 @@ ConversationModelPimpl::slotTransferStatusOngoing(const QString& fileId, datatra
|
|||
auto conversationIdx = indexOf(conversationId);
|
||||
auto* timer = new QTimer();
|
||||
connect(timer, &QTimer::timeout, [=] {
|
||||
updateTransferProgress(timer, conversationId, conversationIdx, interactionId);
|
||||
updateTransferProgress(timer, conversationIdx, interactionId);
|
||||
});
|
||||
timer->start(1000);
|
||||
}
|
||||
|
@ -4281,7 +4387,6 @@ ConversationModelPimpl::updateTransferStatus(const QString& fileId,
|
|||
|
||||
void
|
||||
ConversationModelPimpl::updateTransferProgress(QTimer* timer,
|
||||
const QString&,
|
||||
int conversationIdx,
|
||||
const QString& interactionId)
|
||||
{
|
||||
|
|
|
@ -68,6 +68,20 @@ MessageListModel::find(const QString& msgId)
|
|||
return interactions_.end();
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::findActiveCall(const MapStringString& commit)
|
||||
{
|
||||
iterator it;
|
||||
for (it = interactions_.begin(); it != interactions_.end(); ++it) {
|
||||
const auto& itCommit = it->second.commit;
|
||||
if (itCommit["confId"] == commit["confId"] && itCommit["uri"] == commit["uri"]
|
||||
&& itCommit["device"] == commit["device"]) {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return interactions_.end();
|
||||
}
|
||||
|
||||
iterator
|
||||
MessageListModel::erase(const iterator& it)
|
||||
{
|
||||
|
@ -403,6 +417,13 @@ MessageListModel::dataForItem(item_t item, int, int role) const
|
|||
case Role::Timestamp:
|
||||
return QVariant::fromValue(item.second.timestamp);
|
||||
case Role::Duration:
|
||||
if (!item.second.commit.empty()) {
|
||||
// For swarm, check the commit value
|
||||
if (item.second.commit.find("duration") == item.second.commit.end())
|
||||
return QVariant::fromValue(0);
|
||||
else
|
||||
return QVariant::fromValue(item.second.commit["duration"].toInt() / 1000);
|
||||
}
|
||||
return QVariant::fromValue(item.second.duration);
|
||||
case Role::Type:
|
||||
return QVariant(static_cast<int>(item.second.type));
|
||||
|
@ -416,6 +437,10 @@ MessageListModel::dataForItem(item_t item, int, int role) const
|
|||
return QVariant(item.second.linkified);
|
||||
case Role::ActionUri:
|
||||
return QVariant(item.second.commit["uri"]);
|
||||
case Role::ConfId:
|
||||
return QVariant(item.second.commit["confId"]);
|
||||
case Role::DeviceId:
|
||||
return QVariant(item.second.commit["device"]);
|
||||
case Role::ContactAction:
|
||||
return QVariant(item.second.commit["action"]);
|
||||
case Role::PreviousBodies: {
|
||||
|
|
|
@ -43,6 +43,8 @@ struct Info;
|
|||
X(IsRead) \
|
||||
X(ContactAction) \
|
||||
X(ActionUri) \
|
||||
X(ConfId) \
|
||||
X(DeviceId) \
|
||||
X(LinkPreviewInfo) \
|
||||
X(Linkified) \
|
||||
X(PreviousBodies) \
|
||||
|
@ -84,7 +86,9 @@ public:
|
|||
interaction::Info message,
|
||||
bool beginning = false);
|
||||
iterator find(const QString& msgId);
|
||||
iterator findActiveCall(const MapStringString& commit);
|
||||
iterator erase(const iterator& it);
|
||||
|
||||
constIterator find(const QString& msgId) const;
|
||||
QPair<iterator, bool> insert(std::pair<QString, interaction::Info> message,
|
||||
bool beginning = false);
|
||||
|
|
|
@ -92,8 +92,6 @@ public:
|
|||
Q_EMIT this->volatileAccountDetailsChanged(QString(accountID.c_str()),
|
||||
convertMap(details));
|
||||
}),
|
||||
exportable_callback<ConfigurationSignal::Error>(
|
||||
[this](int code) { Q_EMIT this->errorAlert(code); }),
|
||||
exportable_callback<ConfigurationSignal::CertificateExpired>(
|
||||
[this](const std::string& certId) {
|
||||
Q_EMIT this->certificateExpired(QString(certId.c_str()));
|
||||
|
@ -129,6 +127,11 @@ public:
|
|||
QString(message_id.c_str()),
|
||||
state);
|
||||
}),
|
||||
exportable_callback<libjami::ConfigurationSignal::NeedsHost>(
|
||||
[this](const std::string& account_id, const std::string& conversation_id) {
|
||||
Q_EMIT this->needsHost(QString(account_id.c_str()),
|
||||
QString(conversation_id.c_str()));
|
||||
}),
|
||||
exportable_callback<ConfigurationSignal::IncomingTrustRequest>(
|
||||
[this](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
|
@ -356,6 +359,14 @@ public:
|
|||
QString(conversationId.c_str()),
|
||||
code,
|
||||
QString(what.c_str()));
|
||||
}),
|
||||
exportable_callback<ConfigurationSignal::ActiveCallsChanged>(
|
||||
[this](const std::string& accountId,
|
||||
const std::string& conversationId,
|
||||
const std::vector<std::map<std::string, std::string>>& activeCalls) {
|
||||
Q_EMIT activeCallsChanged(QString(accountId.c_str()),
|
||||
QString(conversationId.c_str()),
|
||||
convertVecMap(activeCalls));
|
||||
})};
|
||||
}
|
||||
|
||||
|
@ -432,7 +443,16 @@ public Q_SLOTS: // METHODS
|
|||
|
||||
QStringList getAccountList()
|
||||
{
|
||||
QStringList temp = convertStringList(libjami::getAccountList());
|
||||
return convertStringList(libjami::getAccountList());
|
||||
}
|
||||
|
||||
VectorMapStringString getActiveCalls(const QString& accountId, const QString& convId)
|
||||
{
|
||||
VectorMapStringString temp;
|
||||
for (const auto& x :
|
||||
libjami::getActiveCalls(accountId.toStdString(), convId.toStdString())) {
|
||||
temp.push_back(convertMap(x));
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
@ -1195,7 +1215,6 @@ Q_SIGNALS: // SIGNALS
|
|||
unsigned detail_code,
|
||||
const QString& detail_str);
|
||||
void stunStatusSuccess(const QString& message);
|
||||
void errorAlert(int code);
|
||||
void volatileAccountDetailsChanged(const QString& accountID, MapStringString details);
|
||||
void certificatePinned(const QString& certId);
|
||||
void certificatePathPinned(const QString& path, const QStringList& certIds);
|
||||
|
@ -1222,6 +1241,7 @@ Q_SIGNALS: // SIGNALS
|
|||
const QString& peer,
|
||||
const QString& messageId,
|
||||
int status);
|
||||
void needsHost(const QString& accountId, const QString& conversationId);
|
||||
void nameRegistrationEnded(const QString& accountId, int status, const QString& name);
|
||||
void registeredNameFound(const QString& accountId,
|
||||
int status,
|
||||
|
@ -1278,6 +1298,9 @@ Q_SIGNALS: // SIGNALS
|
|||
const QString& conversationId,
|
||||
int code,
|
||||
const QString& what);
|
||||
void activeCallsChanged(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const VectorMapStringString& activeCalls);
|
||||
void conversationPreferencesUpdated(const QString& accountId,
|
||||
const QString& conversationId,
|
||||
const MapStringString& message);
|
||||
|
|
Loading…
Add table
Reference in a new issue