- Add Attended transfer feature (contributed by Chad Attermann)

- Fix crash on loading empty chat room
This commit is contained in:
Julien Wadel 2021-09-03 12:25:54 +02:00
parent 756f38e2ff
commit cfdc3069f0
15 changed files with 1235 additions and 1105 deletions

View file

@ -423,6 +423,16 @@
<source>callPause</source>
<translation>HOLD CALL</translation>
</message>
<message>
<source>attendedTransferComplete</source>
<extracomment>&apos;COMPLETE ATTENDED TRANSFER&apos; : Title button, design is in uppercase.</extracomment>
<translation>COMPLETE ATTENDED TRANSFER</translation>
</message>
<message>
<source>attendedTransferCall</source>
<extracomment>&apos;ATTENDED TRANSFER CALL&apos; : Title button, design is in uppercase.</extracomment>
<translation>ATTENDED TRANSFER CALL</translation>
</message>
</context>
<context>
<name>CallsWindow</name>

View file

@ -58,7 +58,7 @@ static void cliCall (QHash<QString, QString> &args) {
app->processArguments(args);
app->initContentApp();
}else
CoreManager::getInstance()->getCallsListModel()->launchAudioCall(args["sip-address"]);
CoreManager::getInstance()->getCallsListModel()->launchAudioCall(args["sip-address"], "");
}
static void cliBye (QHash<QString, QString> &args) {
@ -89,7 +89,7 @@ static void cliJoinConference (QHash<QString, QString> &args) {
}
args["method"] = QStringLiteral("join-conference");
coreManager->getCallsListModel()->launchAudioCall(sipAddress, args);
coreManager->getCallsListModel()->launchAudioCall(sipAddress, "", args);
}
static void cliJoinConferenceAs (QHash<QString, QString> &args) {
@ -121,7 +121,7 @@ static void cliJoinConferenceAs (QHash<QString, QString> &args) {
}
args["method"] = QStringLiteral("join-conference");
coreManager->getCallsListModel()->launchAudioCall(toSipAddress, args);
coreManager->getCallsListModel()->launchAudioCall(toSipAddress, "", args);
}
static void cliInitiateConference (QHash<QString, QString> &args) {

File diff suppressed because it is too large Load diff

View file

@ -30,237 +30,243 @@ class ContactModel;
class ChatRoomModel;
class CallModel : public QObject {
Q_OBJECT;
Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT);
Q_PROPERTY(QString localAddress READ getLocalAddress CONSTANT);
Q_PROPERTY(QString fullPeerAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged);
Q_PROPERTY(QString fullLocalAddress READ getFullLocalAddress CONSTANT);
Q_OBJECT;
Q_PROPERTY(QString peerAddress READ getPeerAddress CONSTANT);
Q_PROPERTY(QString localAddress READ getLocalAddress CONSTANT);
Q_PROPERTY(QString fullPeerAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged);
Q_PROPERTY(QString fullLocalAddress READ getFullLocalAddress CONSTANT);
Q_PROPERTY(ContactModel *contactModel READ getContactModel CONSTANT )
Q_PROPERTY(ChatRoomModel * chatRoomModel READ getChatRoomModel CONSTANT)
/*
Q_PROPERTY(QString sipAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged)
Q_PROPERTY(QString username READ getUsername NOTIFY usernameChanged)
Q_PROPERTY(QString avatar READ getAvatar NOTIFY avatarChanged)
Q_PROPERTY(int presenceStatus READ getPresenceStatus NOTIFY presenceStatusChanged)*/
Q_PROPERTY(CallStatus status READ getStatus NOTIFY statusChanged);
Q_PROPERTY(QString callError READ getCallError NOTIFY callErrorChanged);
Q_PROPERTY(bool isOutgoing READ isOutgoing CONSTANT);
Q_PROPERTY(bool isInConference READ isInConference NOTIFY isInConferenceChanged);
Q_PROPERTY(int duration READ getDuration CONSTANT); // Constants but called with a timer in qml.
Q_PROPERTY(float quality READ getQuality CONSTANT);
Q_PROPERTY(float speakerVu READ getSpeakerVu CONSTANT);
Q_PROPERTY(float microVu READ getMicroVu CONSTANT);
Q_PROPERTY(bool speakerMuted READ getSpeakerMuted WRITE setSpeakerMuted NOTIFY speakerMutedChanged);
Q_PROPERTY(bool microMuted READ getMicroMuted WRITE setMicroMuted NOTIFY microMutedChanged);
Q_PROPERTY(bool pausedByUser READ getPausedByUser WRITE setPausedByUser NOTIFY statusChanged);
Q_PROPERTY(bool videoEnabled READ getVideoEnabled WRITE setVideoEnabled NOTIFY statusChanged);
Q_PROPERTY(bool updating READ getUpdating NOTIFY statusChanged)
Q_PROPERTY(bool recording READ getRecording NOTIFY recordingChanged);
Q_PROPERTY(QVariantList audioStats READ getAudioStats NOTIFY statsUpdated);
Q_PROPERTY(QVariantList videoStats READ getVideoStats NOTIFY statsUpdated);
Q_PROPERTY(CallEncryption encryption READ getEncryption NOTIFY securityUpdated);
Q_PROPERTY(bool isSecured READ isSecured NOTIFY securityUpdated);
Q_PROPERTY(QString localSas READ getLocalSas NOTIFY securityUpdated);
Q_PROPERTY(QString remoteSas READ getRemoteSas NOTIFY securityUpdated);
Q_PROPERTY(QString securedString READ getSecuredString NOTIFY securityUpdated);
Q_PROPERTY(float speakerVolumeGain READ getSpeakerVolumeGain WRITE setSpeakerVolumeGain NOTIFY speakerVolumeGainChanged);
Q_PROPERTY(float microVolumeGain READ getMicroVolumeGain WRITE setMicroVolumeGain NOTIFY microVolumeGainChanged);
Q_PROPERTY(CallStatus status READ getStatus NOTIFY statusChanged);
Q_PROPERTY(QString callError READ getCallError NOTIFY callErrorChanged);
Q_PROPERTY(bool isOutgoing READ isOutgoing CONSTANT);
Q_PROPERTY(bool isInConference READ isInConference NOTIFY isInConferenceChanged);
Q_PROPERTY(int duration READ getDuration CONSTANT); // Constants but called with a timer in qml.
Q_PROPERTY(float quality READ getQuality CONSTANT);
Q_PROPERTY(float speakerVu READ getSpeakerVu CONSTANT);
Q_PROPERTY(float microVu READ getMicroVu CONSTANT);
Q_PROPERTY(bool speakerMuted READ getSpeakerMuted WRITE setSpeakerMuted NOTIFY speakerMutedChanged);
Q_PROPERTY(bool microMuted READ getMicroMuted WRITE setMicroMuted NOTIFY microMutedChanged);
Q_PROPERTY(float speakerVolumeGain READ getSpeakerVolumeGain WRITE setSpeakerVolumeGain NOTIFY speakerVolumeGainChanged);
Q_PROPERTY(float microVolumeGain READ getMicroVolumeGain WRITE setMicroVolumeGain NOTIFY microVolumeGainChanged);
Q_PROPERTY(bool pausedByUser READ getPausedByUser WRITE setPausedByUser NOTIFY statusChanged);
Q_PROPERTY(bool videoEnabled READ getVideoEnabled WRITE setVideoEnabled NOTIFY statusChanged);
Q_PROPERTY(bool updating READ getUpdating NOTIFY statusChanged)
Q_PROPERTY(bool recording READ getRecording NOTIFY recordingChanged);
Q_PROPERTY(QVariantList audioStats READ getAudioStats NOTIFY statsUpdated);
Q_PROPERTY(QVariantList videoStats READ getVideoStats NOTIFY statsUpdated);
Q_PROPERTY(CallEncryption encryption READ getEncryption NOTIFY securityUpdated);
Q_PROPERTY(bool isSecured READ isSecured NOTIFY securityUpdated);
Q_PROPERTY(QString localSas READ getLocalSas NOTIFY securityUpdated);
Q_PROPERTY(QString remoteSas READ getRemoteSas NOTIFY securityUpdated);
Q_PROPERTY(QString securedString READ getSecuredString NOTIFY securityUpdated);
Q_PROPERTY(QString transferAddress READ getTransferAddress WRITE setTransferAddress NOTIFY transferAddressChanged);
public:
enum CallStatus {
CallStatusConnected,
CallStatusEnded,
CallStatusIdle,
CallStatusIncoming,
CallStatusOutgoing,
CallStatusPaused
};
Q_ENUM(CallStatus);
enum CallEncryption {
CallEncryptionNone = int(linphone::MediaEncryption::None),
CallEncryptionDtls = int(linphone::MediaEncryption::DTLS),
CallEncryptionSrtp = int(linphone::MediaEncryption::SRTP),
CallEncryptionZrtp = int(linphone::MediaEncryption::ZRTP)
};
Q_ENUM(CallEncryption);
CallModel (std::shared_ptr<linphone::Call> call);
~CallModel ();
std::shared_ptr<linphone::Call> getCall () const {
return mCall;
}
QString getPeerAddress () const;
QString getLocalAddress () const;
QString getFullPeerAddress () const;
QString getFullLocalAddress () const;
ContactModel *getContactModel() const;
ChatRoomModel * getChatRoomModel() const;
bool isInConference () const {
return mIsInConference;
}
void setRecordFile (const std::shared_ptr<linphone::CallParams> &callParams);
static void setRecordFile (const std::shared_ptr<linphone::CallParams> &callParams, const QString &to);
void updateStats (const std::shared_ptr<const linphone::CallStats> &callStats);
void notifyCameraFirstFrameReceived (unsigned int width, unsigned int height);
Q_INVOKABLE void accept ();
Q_INVOKABLE void acceptWithVideo ();
Q_INVOKABLE void terminate ();
Q_INVOKABLE void askForTransfer ();
Q_INVOKABLE bool transferTo (const QString &sipAddress);
Q_INVOKABLE void acceptVideoRequest ();
Q_INVOKABLE void rejectVideoRequest ();
Q_INVOKABLE void takeSnapshot ();
Q_INVOKABLE void startRecording ();
Q_INVOKABLE void stopRecording ();
Q_INVOKABLE void sendDtmf (const QString &dtmf);
Q_INVOKABLE void verifyAuthenticationToken (bool verify);
Q_INVOKABLE void updateStreams ();
Q_INVOKABLE void toggleSpeakerMute();
void setRemoteDisplayName(const std::string& name);
static constexpr int DtmfSoundDelay = 200;
std::shared_ptr<linphone::Call> mCall;
std::shared_ptr<linphone::Address> mRemoteAddress;
std::shared_ptr<linphone::MagicSearch> mMagicSearch;
enum CallStatus {
CallStatusConnected,
CallStatusEnded,
CallStatusIdle,
CallStatusIncoming,
CallStatusOutgoing,
CallStatusPaused
};
Q_ENUM(CallStatus);
enum CallEncryption {
CallEncryptionNone = int(linphone::MediaEncryption::None),
CallEncryptionDtls = int(linphone::MediaEncryption::DTLS),
CallEncryptionSrtp = int(linphone::MediaEncryption::SRTP),
CallEncryptionZrtp = int(linphone::MediaEncryption::ZRTP)
};
Q_ENUM(CallEncryption);
CallModel (std::shared_ptr<linphone::Call> call);
~CallModel ();
std::shared_ptr<linphone::Call> getCall () const {
return mCall;
}
QString getPeerAddress () const;
QString getLocalAddress () const;
QString getFullPeerAddress () const;
QString getFullLocalAddress () const;
ContactModel *getContactModel() const;
ChatRoomModel * getChatRoomModel() const;
bool isInConference () const {
return mIsInConference;
}
void setRecordFile (const std::shared_ptr<linphone::CallParams> &callParams);
static void setRecordFile (const std::shared_ptr<linphone::CallParams> &callParams, const QString &to);
void updateStats (const std::shared_ptr<const linphone::CallStats> &callStats);
void notifyCameraFirstFrameReceived (unsigned int width, unsigned int height);
Q_INVOKABLE void accept ();
Q_INVOKABLE void acceptWithVideo ();
Q_INVOKABLE void terminate ();
Q_INVOKABLE void askForTransfer ();
Q_INVOKABLE void askForAttendedTransfer ();
Q_INVOKABLE bool transferTo (const QString &sipAddress);
Q_INVOKABLE bool transferToAnother (const QString &peerAddress);
Q_INVOKABLE void acceptVideoRequest ();
Q_INVOKABLE void rejectVideoRequest ();
Q_INVOKABLE void takeSnapshot ();
Q_INVOKABLE void startRecording ();
Q_INVOKABLE void stopRecording ();
Q_INVOKABLE void sendDtmf (const QString &dtmf);
Q_INVOKABLE void verifyAuthenticationToken (bool verify);
Q_INVOKABLE void updateStreams ();
Q_INVOKABLE void toggleSpeakerMute();
void setRemoteDisplayName(const std::string& name);
QString getTransferAddress () const;
void setTransferAddress (const QString &transferAddress);
static void prepareTransfert(std::shared_ptr<linphone::Call> call, const QString& transfertAddress);
std::shared_ptr<linphone::Address> getRemoteAddress()const;
static constexpr int DtmfSoundDelay = 200;
std::shared_ptr<linphone::Call> mCall;
std::shared_ptr<linphone::Address> mRemoteAddress;
std::shared_ptr<linphone::MagicSearch> mMagicSearch;
public slots:
// Set remote display name when a search has been done
void searchReceived(std::list<std::shared_ptr<linphone::SearchResult>> results);
void callEnded();
// Set remote display name when a search has been done
void searchReceived(std::list<std::shared_ptr<linphone::SearchResult>> results);
void callEnded();
signals:
void callErrorChanged (const QString &callError);
void isInConferenceChanged (bool status);
void speakerMutedChanged (bool status);
void microMutedChanged (bool status);
void recordingChanged (bool status);
void statsUpdated ();
void statusChanged (CallStatus status);
void videoRequested ();
void securityUpdated ();
void speakerVolumeGainChanged (float volume);
void microVolumeGainChanged (float volume);
void cameraFirstFrameReceived (unsigned int width, unsigned int height);
void fullPeerAddressChanged();
void callErrorChanged (const QString &callError);
void isInConferenceChanged (bool status);
void speakerMutedChanged (bool status);
void microMutedChanged (bool status);
void recordingChanged (bool status);
void statsUpdated ();
void statusChanged (CallStatus status);
void videoRequested ();
void securityUpdated ();
void speakerVolumeGainChanged (float volume);
void microVolumeGainChanged (float volume);
void cameraFirstFrameReceived (unsigned int width, unsigned int height);
void fullPeerAddressChanged();
void transferAddressChanged (const QString &transferAddress);
private:
void handleCallEncryptionChanged (const std::shared_ptr<linphone::Call> &call);
void handleCallStateChanged (const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);
void accept (bool withVideo);
void stopAutoAnswerTimer () const;
CallStatus getStatus () const;
bool isOutgoing () const {
return mCall->getDir() == linphone::Call::Dir::Outgoing;
}
void updateIsInConference ();
void acceptWithAutoAnswerDelay ();
QString getCallError () const;
void setCallErrorFromReason (linphone::Reason reason);
int getDuration () const;
float getQuality () const;
float getMicroVu () const;
float getSpeakerVu () const;
bool getSpeakerMuted () const;
void setSpeakerMuted (bool status);
bool getMicroMuted () const;
void setMicroMuted (bool status);
bool getPausedByUser () const;
void setPausedByUser (bool status);
bool getVideoEnabled () const;
void setVideoEnabled (bool status);
bool getUpdating () const;
bool getRecording () const;
CallEncryption getEncryption () const;
bool isSecured () const;
QString getLocalSas () const;
QString getRemoteSas () const;
QString getSecuredString () const;
QVariantList getAudioStats () const;
QVariantList getVideoStats () const;
void updateStats (const std::shared_ptr<const linphone::CallStats> &callStats, QVariantList &statsList);
QString iceStateToString (linphone::IceState state) const;
float getSpeakerVolumeGain () const;
void setSpeakerVolumeGain (float volume);
float getMicroVolumeGain () const;
void setMicroVolumeGain (float volume);
QString generateSavedFilename () const;
static QString generateSavedFilename (const QString &from, const QString &to);
bool mIsInConference = false;
bool mPausedByRemote = false;
bool mPausedByUser = false;
bool mRecording = false;
bool mWasConnected = false;
bool mNotifyCameraFirstFrameReceived = true;
QString mCallError;
QVariantList mAudioStats;
QVariantList mVideoStats;
std::shared_ptr<SearchHandler> mSearch;
void handleCallEncryptionChanged (const std::shared_ptr<linphone::Call> &call);
void handleCallStateChanged (const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);
void accept (bool withVideo);
void stopAutoAnswerTimer () const;
CallStatus getStatus () const;
bool isOutgoing () const {
return mCall->getDir() == linphone::Call::Dir::Outgoing;
}
void updateIsInConference ();
void acceptWithAutoAnswerDelay ();
QString getCallError () const;
void setCallErrorFromReason (linphone::Reason reason);
int getDuration () const;
float getQuality () const;
float getMicroVu () const;
float getSpeakerVu () const;
bool getSpeakerMuted () const;
void setSpeakerMuted (bool status);
bool getMicroMuted () const;
void setMicroMuted (bool status);
bool getPausedByUser () const;
void setPausedByUser (bool status);
bool getVideoEnabled () const;
void setVideoEnabled (bool status);
bool getUpdating () const;
bool getRecording () const;
CallEncryption getEncryption () const;
bool isSecured () const;
QString getLocalSas () const;
QString getRemoteSas () const;
QString getSecuredString () const;
QVariantList getAudioStats () const;
QVariantList getVideoStats () const;
void updateStats (const std::shared_ptr<const linphone::CallStats> &callStats, QVariantList &statsList);
QString iceStateToString (linphone::IceState state) const;
float getSpeakerVolumeGain () const;
void setSpeakerVolumeGain (float volume);
float getMicroVolumeGain () const;
void setMicroVolumeGain (float volume);
QString generateSavedFilename () const;
static QString generateSavedFilename (const QString &from, const QString &to);
bool mIsInConference = false;
bool mPausedByRemote = false;
bool mPausedByUser = false;
bool mRecording = false;
bool mWasConnected = false;
bool mNotifyCameraFirstFrameReceived = true;
QString mCallError;
QVariantList mAudioStats;
QVariantList mVideoStats;
std::shared_ptr<SearchHandler> mSearch;
QString mTransferAddress;
};
#endif // CALL_MODEL_H_

View file

@ -93,15 +93,27 @@ QVariant CallsListModel::data (const QModelIndex &index, int role) const {
return QVariant();
}
CallModel *CallsListModel::findCallModelFromPeerAddress (const QString &peerAddress) const {
std::shared_ptr<linphone::Address> address = Utils::interpretUrl(peerAddress);
auto it = find_if(mList.begin(), mList.end(), [address](CallModel *callModel) {
return callModel->getRemoteAddress()->weakEqual(address);
});
return it != mList.end() ? *it : nullptr;
}
// -----------------------------------------------------------------------------
void CallsListModel::askForTransfer (CallModel *callModel) {
emit callTransferAsked(callModel);
}
void CallsListModel::askForAttendedTransfer (CallModel *callModel) {
emit callAttendedTransferAsked(callModel);
}
// -----------------------------------------------------------------------------
void CallsListModel::launchAudioCall (const QString &sipAddress, const QHash<QString, QString> &headers) const {
void CallsListModel::launchAudioCall (const QString &sipAddress, const QString& prepareTransfertAddress, const QHash<QString, QString> &headers) const {
CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true;
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
@ -122,23 +134,23 @@ void CallsListModel::launchAudioCall (const QString &sipAddress, const QHash<QSt
shared_ptr<linphone::ProxyConfig> currentProxyConfig = core->getDefaultProxyConfig();
if(currentProxyConfig){
if(!CoreManager::getInstance()->getSettingsModel()->getWaitRegistrationForCall() || currentProxyConfig->getState() == linphone::RegistrationState::Ok)
core->inviteAddressWithParams(address, params);
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
else{
QObject * context = new QObject();
QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context,
[address,core,params,currentProxyConfig, context](const std::shared_ptr<linphone::ProxyConfig> &proxyConfig, linphone::RegistrationState state) mutable {
[address,core,params,currentProxyConfig,prepareTransfertAddress, context](const std::shared_ptr<linphone::ProxyConfig> &proxyConfig, linphone::RegistrationState state) mutable {
if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
delete context;
context = nullptr;
core->inviteAddressWithParams(address, params);
}
});
}
}else
core->inviteAddressWithParams(address, params);
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
}
void CallsListModel::launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash<QString, QString> &headers) const {
void CallsListModel::launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash<QString, QString> &headers, const QString& prepareTransfertAddress) const {
CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true;
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
@ -160,28 +172,28 @@ void CallsListModel::launchSecureAudioCall (const QString &sipAddress, LinphoneE
params->setMediaEncryption(LinphoneEnums::toLinphone(encryption));
if(currentProxyConfig){
if(!CoreManager::getInstance()->getSettingsModel()->getWaitRegistrationForCall() || currentProxyConfig->getState() == linphone::RegistrationState::Ok)
core->inviteAddressWithParams(address, params);
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
else{
QObject * context = new QObject();
QObject::connect(CoreManager::getInstance()->getHandlers().get(), &CoreHandlers::registrationStateChanged,context,
[address,core,params,currentProxyConfig, context](const std::shared_ptr<linphone::ProxyConfig> &proxyConfig, linphone::RegistrationState state) mutable {
[address,core,params,currentProxyConfig,prepareTransfertAddress, context](const std::shared_ptr<linphone::ProxyConfig> &proxyConfig, linphone::RegistrationState state) mutable {
if(context && proxyConfig==currentProxyConfig && state==linphone::RegistrationState::Ok){
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
delete context;
context = nullptr;
core->inviteAddressWithParams(address, params);
}
});
}
}else
core->inviteAddressWithParams(address, params);
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
}
void CallsListModel::launchVideoCall (const QString &sipAddress) const {
void CallsListModel::launchVideoCall (const QString &sipAddress, const QString& prepareTransfertAddress) const {
CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = true;
shared_ptr<linphone::Core> core = CoreManager::getInstance()->getCore();
if (!core->videoSupported()) {
qWarning() << QStringLiteral("Unable to launch video call. (Video not supported.) Launching audio call...");
launchAudioCall(sipAddress);
launchAudioCall(sipAddress, prepareTransfertAddress, {});
return;
}
@ -193,7 +205,7 @@ void CallsListModel::launchVideoCall (const QString &sipAddress) const {
params->enableVideo(true);
params->setProxyConfig(core->getDefaultProxyConfig());
CallModel::setRecordFile(params, Utils::coreStringToAppString(address->getUsername()));
core->inviteAddressWithParams(address, params);
CallModel::prepareTransfert(core->inviteAddressWithParams(address, params), prepareTransfertAddress);
}
ChatRoomModel* CallsListModel::launchSecureChat (const QString &sipAddress) const {
@ -249,7 +261,7 @@ ChatRoomModel* CallsListModel::createChat (const QString &participantAddress) co
params->setBackend(linphone::ChatRoomBackend::Basic);
std::shared_ptr<linphone::ChatRoom> chatRoom = core->createChatRoom(params, localAddress, participants);
if( chatRoom != nullptr){
auto timelineList = CoreManager::getInstance()->getTimelineListModel();
auto timeline = timelineList->getTimeline(chatRoom, true);
@ -315,7 +327,7 @@ QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& se
address = Utils::interpretUrl(participant);
}
if( address)
chatRoomParticipants.push_back( address );
chatRoomParticipants.push_back( address );
}
auto proxy = core->getDefaultProxyConfig();
params->enableEncryption(securityLevel>0);
@ -344,7 +356,7 @@ QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& se
chatRoom = core->createChatRoom(params, localAddress, chatRoomParticipants);
if(chatRoom != nullptr && admins.size() > 0)
ChatRoomInitializer::setAdminsAsync(params->getSubject(), params->getBackend(), params->groupEnabled(), admins );
timeline = timelineList->getTimeline(chatRoom, false);
timeline = timelineList->getTimeline(chatRoom, false);
}else{
if(admins.size() > 0){
ChatRoomInitializer::setAdminsSync(chatRoom, admins);
@ -391,6 +403,7 @@ void CallsListModel::terminateCall (const QString& sipAddress) const{
}
}
}
// -----------------------------------------------------------------------------
static void joinConference (const shared_ptr<linphone::Call> &call) {

View file

@ -33,53 +33,56 @@ class ChatRoomModel;
class CoreHandlers;
class CallsListModel : public QAbstractListModel {
Q_OBJECT
Q_OBJECT
public:
CallsListModel (QObject *parent = Q_NULLPTR);
int rowCount (const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
void askForTransfer (CallModel *callModel);
Q_INVOKABLE void launchAudioCall (const QString &sipAddress, const QHash<QString, QString> &headers = {}) const;
Q_INVOKABLE void launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash<QString, QString> &headers = {}) const;
Q_INVOKABLE void launchVideoCall (const QString &sipAddress) const;
Q_INVOKABLE ChatRoomModel* launchSecureChat (const QString &sipAddress) const;
Q_INVOKABLE QVariantMap launchChat(const QString &sipAddress, const int& securityLevel) const;
Q_INVOKABLE ChatRoomModel* createChat (const QString &participantAddress) const;
Q_INVOKABLE ChatRoomModel* createChat (const CallModel * ) const;
Q_INVOKABLE bool createSecureChat (const QString& subject, const QString &participantAddress) const;
Q_INVOKABLE QVariantMap createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants, const bool& selectAfterCreation) const;
Q_INVOKABLE int getRunningCallsNumber () const;
Q_INVOKABLE void terminateAllCalls () const;
Q_INVOKABLE void terminateCall (const QString& sipAddress) const;
CallsListModel (QObject *parent = Q_NULLPTR);
int rowCount (const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames () const override;
QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const override;
CallModel *findCallModelFromPeerAddress (const QString &peerAddress) const;
void askForTransfer (CallModel *callModel);
void askForAttendedTransfer (CallModel *callModel);
Q_INVOKABLE void launchAudioCall (const QString &sipAddress, const QString& prepareTransfertAddress = "", const QHash<QString, QString> &headers = {}) const;
Q_INVOKABLE void launchSecureAudioCall (const QString &sipAddress, LinphoneEnums::MediaEncryption encryption, const QHash<QString, QString> &headers = {}, const QString& prepareTransfertAddress = "") const;
Q_INVOKABLE void launchVideoCall (const QString &sipAddress, const QString& prepareTransfertAddress = "") const;
Q_INVOKABLE ChatRoomModel* launchSecureChat (const QString &sipAddress) const;
Q_INVOKABLE QVariantMap launchChat(const QString &sipAddress, const int& securityLevel) const;
Q_INVOKABLE ChatRoomModel* createChat (const QString &participantAddress) const;
Q_INVOKABLE ChatRoomModel* createChat (const CallModel * ) const;
Q_INVOKABLE bool createSecureChat (const QString& subject, const QString &participantAddress) const;
Q_INVOKABLE QVariantMap createChatRoom(const QString& subject, const int& securityLevel, const QVariantList& participants, const bool& selectAfterCreation) const;
Q_INVOKABLE int getRunningCallsNumber () const;
Q_INVOKABLE void terminateAllCalls () const;
Q_INVOKABLE void terminateCall (const QString& sipAddress) const;
signals:
void callRunning (int index, CallModel *callModel);
void callTransferAsked (CallModel *callModel);
void callMissed (CallModel *callModel);
void callRunning (int index, CallModel *callModel);
void callTransferAsked (CallModel *callModel);
void callAttendedTransferAsked (CallModel *callModel);
void callMissed (CallModel *callModel);
private:
bool removeRow (int row, const QModelIndex &parent = QModelIndex());
bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override;
void handleCallStateChanged (const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);
void addCall (const std::shared_ptr<linphone::Call> &call);
void removeCall (const std::shared_ptr<linphone::Call> &call);
void removeCallCb (CallModel *callModel);
QList<CallModel *> mList;
std::shared_ptr<CoreHandlers> mCoreHandlers;
bool removeRow (int row, const QModelIndex &parent = QModelIndex());
bool removeRows (int row, int count, const QModelIndex &parent = QModelIndex()) override;
void handleCallStateChanged (const std::shared_ptr<linphone::Call> &call, linphone::Call::State state);
void addCall (const std::shared_ptr<linphone::Call> &call);
void removeCall (const std::shared_ptr<linphone::Call> &call);
void removeCallCb (CallModel *callModel);
QList<CallModel *> mList;
std::shared_ptr<CoreHandlers> mCoreHandlers;
};
#endif // CALLS_LIST_MODEL_H_

View file

@ -139,17 +139,19 @@ void ChatRoomProxyModel::compose (const QString& text) {
// -----------------------------------------------------------------------------
void ChatRoomProxyModel::loadMoreEntries () {
int count = rowCount();
int parentCount = sourceModel()->rowCount();
if (count == mMaxDisplayedEntries)
mMaxDisplayedEntries += EntriesChunkSize;
if (count + 10 >= parentCount) // Magic number : try to load more entries if near to max event count
mChatRoomModel->loadMoreEntries();
invalidateFilter();
count = rowCount() - count;
if (count > 0)
emit moreEntriesLoaded(count);
if(mChatRoomModel ) {
int count = rowCount();
int parentCount = sourceModel()->rowCount();
if (count == mMaxDisplayedEntries)
mMaxDisplayedEntries += EntriesChunkSize;
if (count + 10 >= parentCount) // Magic number : try to load more entries if near to max event count
mChatRoomModel->loadMoreEntries();
invalidateFilter();
count = rowCount() - count;
if (count > 0)
emit moreEntriesLoaded(count);
}
}
void ChatRoomProxyModel::setEntryTypeFilter (int type) {

View file

@ -30,123 +30,155 @@
// -----------------------------------------------------------------------------
function getParams (call) {
if (!call) {
return
}
var CallModel = Linphone.CallModel
var status = call.status
if (status === CallModel.CallStatusConnected) {
var optActions = []
if (Linphone.SettingsModel.callPauseEnabled) {
optActions.push({
handler: (function () { call.pausedByUser = true }),
name: qsTr('callPause')
})
}
return {
actions: optActions.concat([{
handler: call.askForTransfer,
name: qsTr('transferCall')
}, {
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'connected'
}
}
if (status === CallModel.CallStatusEnded) {
return {
string: 'ended'
}
}
if (status === CallModel.CallStatusIncoming) {
var optActions = []
if (Linphone.SettingsModel.videoSupported) {
optActions.push({
handler: call.acceptWithVideo,
name: qsTr('acceptVideoCall')
})
}
return {
actions: [{
handler: (function () { call.accept() }),
name: qsTr('acceptAudioCall')
}].concat(optActions).concat([{
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'incoming'
}
}
if (status === CallModel.CallStatusOutgoing) {
return {
component: callAction,
handler: call.terminate,
icon: 'hangup',
string: 'outgoing'
}
}
if (status === CallModel.CallStatusPaused) {
var optActions = []
if (call.pausedByUser) {
optActions.push({
handler: (function () { call.pausedByUser = false }),
name: qsTr('resumeCall')
})
} else if (Linphone.SettingsModel.callPauseEnabled) {
optActions.push({
handler: (function () { call.pausedByUser = true }),
name: qsTr('callPause')
})
}
return {
actions: optActions.concat([{
handler: call.askForTransfer,
name: qsTr('transferCall')
}, {
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'paused'
}
}
if (!call) {
return
}
var CallModel = Linphone.CallModel
var status = call.status
if (status === CallModel.CallStatusConnected) {
var optActions = []
if (Linphone.SettingsModel.callPauseEnabled) {
optActions.push({
handler: (function () { call.pausedByUser = true }),
name: qsTr('callPause')
})
}
if (call.transferAddress !== '') {
optActions.push({
handler: call.askForTransfer,
//: 'COMPLETE ATTENDED TRANSFER' : Title button, design is in uppercase.
name: qsTr('attendedTransferComplete')
})
}
else {
optActions.push({
handler: call.askForTransfer,
name: qsTr('transferCall')
})
optActions.push({
handler: call.askForAttendedTransfer,
//: 'ATTENDED TRANSFER CALL' : Title button, design is in uppercase.
name: qsTr('attendedTransferCall')
})
}
return {
actions: optActions.concat([{
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'connected'
}
}
if (status === CallModel.CallStatusEnded) {
return {
string: 'ended'
}
}
if (status === CallModel.CallStatusIncoming) {
var optActions = []
if (Linphone.SettingsModel.videoSupported) {
optActions.push({
handler: call.acceptWithVideo,
name: qsTr('acceptVideoCall')
})
}
return {
actions: [{
handler: (function () { call.accept() }),
name: qsTr('acceptAudioCall')
}].concat(optActions).concat([{
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'incoming'
}
}
if (status === CallModel.CallStatusOutgoing) {
return {
component: callAction,
handler: call.terminate,
icon: 'hangup',
string: 'outgoing'
}
}
if (status === CallModel.CallStatusPaused) {
var optActions = []
if (call.pausedByUser) {
optActions.push({
handler: (function () { call.pausedByUser = false }),
name: qsTr('resumeCall')
})
} else if (Linphone.SettingsModel.callPauseEnabled) {
optActions.push({
handler: (function () { call.pausedByUser = true }),
name: qsTr('callPause')
})
}
if (call.transferAddress !== '') {
optActions.push({
handler: call.askForTransfer,
//: 'COMPLETE ATTENDED TRANSFER' : Title button, design is in uppercase.
name: qsTr('attendedTransferComplete')
})
}
else {
optActions.push({
handler: call.askForTransfer,
name: qsTr('transferCall')
})
optActions.push({
handler: call.askForAttendedTransfer,
//: 'ATTENDED TRANSFER CALL' : Title button, design is in uppercase.
name: qsTr('attendedTransferCall')
})
}
return {
actions: optActions.concat([{
handler: call.terminate,
name: qsTr('terminateCall')
}]),
component: callActions,
string: 'paused'
}
}
}
function updateSelectedCall (call, index) {
calls._selectedCall = call
if (index != null) {
calls.currentIndex = index
}
calls._selectedCall = call
if (index != null) {
calls.currentIndex = index
}
}
function resetSelectedCall () {
updateSelectedCall(null, -1)
updateSelectedCall(null, -1)
}
function setIndexWithCall (call) {
var count = calls.count
var model = calls.model
for (var i = 0; i < count; i++) {
if (call === model.data(model.index(i, 0))) {
updateSelectedCall(call, i)
return
}
}
updateSelectedCall(call, -1)
var count = calls.count
var model = calls.model
for (var i = 0; i < count; i++) {
if (call === model.data(model.index(i, 0))) {
updateSelectedCall(call, i)
return
}
}
updateSelectedCall(call, -1)
}
// -----------------------------------------------------------------------------
@ -154,23 +186,23 @@ function setIndexWithCall (call) {
// -----------------------------------------------------------------------------
function handleCountChanged (count) {
if (count === 0) {
return
}
var call = calls._selectedCall
if (call == null) {
if (calls.conferenceModel.count > 0) {
return
}
var model = calls.model
var index = count - 1
updateSelectedCall(model.data(model.index(index, 0)), index)
} else {
setIndexWithCall(call)
}
if (count === 0) {
return
}
var call = calls._selectedCall
if (call == null) {
if (calls.conferenceModel.count > 0) {
return
}
var model = calls.model
var index = count - 1
updateSelectedCall(model.data(model.index(index, 0)), index)
} else {
setIndexWithCall(call)
}
}
// -----------------------------------------------------------------------------
@ -178,36 +210,36 @@ function handleCountChanged (count) {
// -----------------------------------------------------------------------------
function handleCallRunning (call) {
if (!call.isInConference) {
setIndexWithCall(call)
}
if (!call.isInConference) {
setIndexWithCall(call)
}
}
function handleRowsAboutToBeRemoved (_, first, last) {
var index = calls.currentIndex
if (index >= first && index <= last) {
resetSelectedCall()
}
var index = calls.currentIndex
if (index >= first && index <= last) {
resetSelectedCall()
}
}
function handleRowsInserted (_, first, last) {
// The last inserted outgoing element become the selected call.
var model = calls.model
for (var index = last; index >= first; index--) {
var call = model.data(model.index(index, 0))
if (call.isOutgoing && !call.isInConference) {
updateSelectedCall(call)
return
}
}
// First received call.
if (first === 0 && model.rowCount() === 1) {
var call = model.data(model.index(0, 0))
if (!call.isInConference) {
updateSelectedCall(model.data(model.index(0, 0)))
}
}
// The last inserted outgoing element become the selected call.
var model = calls.model
for (var index = last; index >= first; index--) {
var call = model.data(model.index(index, 0))
if (call.isOutgoing && !call.isInConference) {
updateSelectedCall(call)
return
}
}
// First received call.
if (first === 0 && model.rowCount() === 1) {
var call = model.data(model.index(0, 0))
if (!call.isInConference) {
updateSelectedCall(model.data(model.index(0, 0)))
}
}
}

View file

@ -245,7 +245,7 @@ Rectangle {
borderColor: ChatStyle.sendArea.border.color
topWidth: ChatStyle.sendArea.border.width
visible: SettingsModel.chatEnabled && !proxyModel.chatRoomModel.hasBeenLeft
visible: SettingsModel.chatEnabled && proxyModel.chatRoomModel && !proxyModel.chatRoomModel.hasBeenLeft
DroppableTextArea {
id: textArea

View file

@ -28,86 +28,109 @@
// =============================================================================
function handleClosing (close) {
var callsList = Linphone.CallsListModel
window.detachVirtualWindow()
if (callsList.getRunningCallsNumber() === 0) {
return
}
window.attachVirtualWindow(Utils.buildDialogUri('ConfirmDialog'), {
descriptionText: qsTr('acceptClosingDescription')
}, function (status) {
if (status) {
callsList.terminateAllCalls()
window.close()
}
})
close.accepted = false
var callsList = Linphone.CallsListModel
window.detachVirtualWindow()
if (callsList.getRunningCallsNumber() === 0) {
return
}
window.attachVirtualWindow(Utils.buildDialogUri('ConfirmDialog'), {
descriptionText: qsTr('acceptClosingDescription')
}, function (status) {
if (status) {
callsList.terminateAllCalls()
window.close()
}
})
close.accepted = false
}
// -----------------------------------------------------------------------------
function openCallSipAddress () {
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallSipAddress.qml'))
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallSipAddress.qml'))
}
function openConferenceManager (params, exitHandler) {
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ConferenceManager.qml'), params, exitHandler)
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/ConferenceManager.qml'), params, exitHandler)
}
// -----------------------------------------------------------------------------
// Used to get Component based from Call Status
function getContent () {
var call = window.call
if (call == null) {
return conference
}
var status = call.status
if (status == null) {
return calls.conferenceModel.count > 0 ? conference : null
}
var CallModel = Linphone.CallModel
if (status === CallModel.CallStatusIncoming) {
return incomingCall
}
if (status === CallModel.CallStatusOutgoing) {
return outgoingCall
}
if (status === CallModel.CallStatusEnded) {
return endedCall
}
return incall
var call = window.call
if (call == null) {
return conference
}
var status = call.status
if (status == null) {
return calls.conferenceModel.count > 0 ? conference : null
}
var CallModel = Linphone.CallModel
if (status === CallModel.CallStatusIncoming) {
return incomingCall
}
if (status === CallModel.CallStatusOutgoing) {
return outgoingCall
}
if (status === CallModel.CallStatusEnded) {
return endedCall
}
return incall
}
// -----------------------------------------------------------------------------
function handleCallTransferAsked (call) {
if (!call) {
return
}
if (!call) {
return
}
if (call.transferAddress !== '') {
console.debug('Attended transfer to call ' + call.transferAddress)
call.transferToAnother(call.transferAddress)
return
}
window.detachVirtualWindow()
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallTransfer.qml'), {
call: call,
attended: false
})
}
window.detachVirtualWindow()
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallTransfer.qml'), {
call: call
})
function handleCallAttendedTransferAsked (call) {
if (!call) {
return
}
if (call.transferAddress !== '') {
console.debug('Attended transfer to call ' + call.transferAddress)
call.transferToAnother(call.transferAddress)
return
}
window.detachVirtualWindow()
window.attachVirtualWindow(Qt.resolvedUrl('Dialogs/CallTransfer.qml'), {
call: call,
attended: true
})
}
function windowMustBeClosed () {
return Linphone.CallsListModel.rowCount() === 0 && !window.virtualWindowVisible
return Linphone.CallsListModel.rowCount() === 0 && !window.virtualWindowVisible
}
function tryToCloseWindow () {
if (windowMustBeClosed()) {
// Workaround, it's necessary to use a timeout because at last call termination
// a segfault is emit in `QOpenGLContext::functions() const ()`.
Utils.setTimeout(window, 0, function () { windowMustBeClosed() && window.close() })
}
if (windowMustBeClosed()) {
// Workaround, it's necessary to use a timeout because at last call termination
// a segfault is emit in `QOpenGLContext::functions() const ()`.
Utils.setTimeout(window, 0, function () { windowMustBeClosed() && window.close() })
}
}

View file

@ -253,6 +253,7 @@ Window {
Connections {
target: CallsListModel
onCallTransferAsked: Logic.handleCallTransferAsked(callModel)
onCallAttendedTransferAsked: Logic.handleCallAttendedTransferAsked(callModel)
onRowsRemoved: Logic.tryToCloseWindow()
}
}

View file

@ -72,7 +72,7 @@ DialogPlus {
secure:0,
visible:true,
handler: function (entry) {
CallsListModel.launchAudioCall(entry.sipAddress)
CallsListModel.launchAudioCall(entry.sipAddress, "")
exit(1)
}
}]

View file

@ -9,93 +9,98 @@ import App.Styles 1.0
// =============================================================================
DialogPlus {
id: callTransfer
// ---------------------------------------------------------------------------
property var call
// ---------------------------------------------------------------------------
buttons: [
TextButtonA {
text: qsTr('cancel')
onClicked: exit(0)
}
]
buttonsAlignment: Qt.AlignCenter
descriptionText: qsTr('callTransferDescription')
height: CallTransferStyle.height + 30
width: CallTransferStyle.width
onCallChanged: !call && exit(0)
// ---------------------------------------------------------------------------
ColumnLayout {
anchors.fill: parent
spacing: 0
// -------------------------------------------------------------------------
// Contact.
// -------------------------------------------------------------------------
Contact {
Layout.fillWidth: true
entry: SipAddressesModel.getSipAddressObserver(call ? call.fullPeerAddress : '', call ? call.fullLocalAddress : '')
}
// -------------------------------------------------------------------------
// Address selector.
// -------------------------------------------------------------------------
Item {
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
spacing: CallTransferStyle.spacing
TextField {
id: filter
Layout.fillWidth: true
icon: 'search'
onTextChanged: sipAddressesModel.setFilter(text)
}
ScrollableListViewField {
Layout.fillHeight: true
Layout.fillWidth: true
SipAddressesView {
anchors.fill: parent
actions: [{
icon: 'transfer',
handler: function (entry) {
callTransfer.call.transferTo(entry.sipAddress)
exit(1)
}
}]
genSipAddress: filter.text
model: SearchSipAddressesModel {
id: sipAddressesModel
}
onEntryClicked: actions[0].handler(entry)
}
}
}
}
}
id: callTransfer
// ---------------------------------------------------------------------------
property var call
property bool attended: false
// ---------------------------------------------------------------------------
buttons: [
TextButtonA {
text: qsTr('cancel')
onClicked: exit(0)
}
]
buttonsAlignment: Qt.AlignCenter
descriptionText: qsTr('callTransferDescription')
height: CallTransferStyle.height + 30
width: CallTransferStyle.width
onCallChanged: !call && exit(0)
// ---------------------------------------------------------------------------
ColumnLayout {
anchors.fill: parent
spacing: 0
// -------------------------------------------------------------------------
// Contact.
// -------------------------------------------------------------------------
Contact {
Layout.fillWidth: true
entry: SipAddressesModel.getSipAddressObserver(call ? call.fullPeerAddress : '', call ? call.fullLocalAddress : '')
}
// -------------------------------------------------------------------------
// Address selector.
// -------------------------------------------------------------------------
Item {
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
anchors.fill: parent
spacing: CallTransferStyle.spacing
TextField {
id: filter
Layout.fillWidth: true
icon: 'search'
onTextChanged: sipAddressesModel.setFilter(text)
}
ScrollableListViewField {
Layout.fillHeight: true
Layout.fillWidth: true
SipAddressesView {
anchors.fill: parent
actions: [{
icon: 'transfer',
handler: function (entry) {
if (attended) {
var call = CallsListModel.launchAudioCall(entry.sipAddress, callTransfer.call.peerAddress)
} else {
callTransfer.call.transferTo(entry.sipAddress)
}
exit(1)
}
}]
genSipAddress: filter.text
model: SearchSipAddressesModel {
id: sipAddressesModel
}
onEntryClicked: actions[0].handler(entry)
}
}
}
}
}
}

View file

@ -259,7 +259,6 @@ ColumnLayout {
icon: 'group_chat'
visible: SettingsModel.outgoingCallsEnabled && conversation.haveMoreThanOneParticipants && conversation.haveLessThanMinParticipantsForCall && !conversation.hasBeenLeft
//onClicked: CallsListModel.launchAudioCall(conversation.chatRoomModel)
onClicked: Logic.openConferenceManager({chatRoomModel:conversation.chatRoomModel, autoCall:true})
TooltipArea {
//: "Call all chat room's participants" : tooltip on a button for calling all participant in the current chat room

View file

@ -182,10 +182,10 @@ ApplicationWindow {
}
}
onLaunchCall: CallsListModel.launchAudioCall(sipAddress)
onLaunchCall: CallsListModel.launchAudioCall(sipAddress, "")
onLaunchChat: CallsListModel.launchChat( sipAddress,0 )
onLaunchSecureChat: CallsListModel.launchChat( sipAddress,1 )
onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress)
onLaunchVideoCall: CallsListModel.launchVideoCall(sipAddress, "")
}