diff --git a/CHANGELOG.md b/CHANGELOG.md index a0925c545..0ccfeacda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 5.0.0 - [undefined] +## 5.0.0 - 2022-12-07 ### Added - Video conference and iCalendars. @@ -13,8 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New call layouts. - Display a waiting room before going into a conference. - Log viewer. +- Read contacts from all friends lists. - Option to set the display name in "using an account" tab of assistant. - Long pressed buttons. +- Date and Time pickers. - Phone dialpad on main window. - Animated file in chats/notifications. - Round progress bar for transferring a file and allow to cancel it. @@ -25,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mark as Read synchronized between devices. - Merge messages into one notification to avoid spam. - Design overhaul on calls. +- Audio devices can be changed while being in call. - Use a cryptographic checksum when downloading openH264 from CISCO (Update to 2.2.0) ### Fixed diff --git a/linphone-app/CMakeLists.txt b/linphone-app/CMakeLists.txt index 656baf131..431756981 100644 --- a/linphone-app/CMakeLists.txt +++ b/linphone-app/CMakeLists.txt @@ -204,6 +204,7 @@ set(SOURCES src/components/core/event-count-notifier/AbstractEventCountNotifier.cpp src/components/file/FileDownloader.cpp src/components/file/FileExtractor.cpp + src/components/friend/FriendListListener.cpp src/components/history/HistoryModel.cpp src/components/history/HistoryProxyModel.cpp src/components/ldap/LdapModel.cpp @@ -338,6 +339,7 @@ set(HEADERS src/components/core/event-count-notifier/AbstractEventCountNotifier.hpp src/components/file/FileDownloader.hpp src/components/file/FileExtractor.hpp + src/components/friend/FriendListListener.hpp src/components/history/HistoryModel.hpp src/components/history/HistoryProxyModel.hpp src/components/ldap/LdapModel.hpp diff --git a/linphone-app/assets/icons/genicons.sh b/linphone-app/assets/icons/genicons.sh index 858411db9..360f52d04 100755 --- a/linphone-app/assets/icons/genicons.sh +++ b/linphone-app/assets/icons/genicons.sh @@ -25,3 +25,4 @@ do inkscape -z -e hicolor/${i}x${i}/apps/icon.png -w $i -h $i ../images/linphone_logo.svg done convert hicolor/16x16/apps/icon.png hicolor/22x22/apps/icon.png hicolor/24x24/apps/icon.png hicolor/32x32/apps/icon.png hicolor/64x64/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png -colors 256 ../icon.ico +png2icns ../../cmake_builder/linphone_package/macos/linphone.icns hicolor/16x16/apps/icon.png hicolor/32x32/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png diff --git a/linphone-app/assets/icons/genicons_1.0.sh b/linphone-app/assets/icons/genicons_1.0.sh index 2994f1b34..2ff6a9242 100755 --- a/linphone-app/assets/icons/genicons_1.0.sh +++ b/linphone-app/assets/icons/genicons_1.0.sh @@ -25,3 +25,4 @@ do inkscape -z --export-type=png --export-file=hicolor/${i}x${i}/apps/icon.png -w $i -h $i ../images/linphone_logo.svg done convert hicolor/16x16/apps/icon.png hicolor/22x22/apps/icon.png hicolor/24x24/apps/icon.png hicolor/32x32/apps/icon.png hicolor/64x64/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png -colors 256 ../icon.ico +png2icns ../../cmake_builder/linphone_package/macos/linphone.icns hicolor/16x16/apps/icon.png hicolor/32x32/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png diff --git a/linphone-app/assets/icons/genicons_1.1.sh b/linphone-app/assets/icons/genicons_1.1.sh index 19982c5e0..91d2b2fdc 100755 --- a/linphone-app/assets/icons/genicons_1.1.sh +++ b/linphone-app/assets/icons/genicons_1.1.sh @@ -25,3 +25,4 @@ do inkscape -z --export-type=png --export-filename=hicolor/${i}x${i}/apps/icon.png -w $i -h $i ../images/linphone_logo.svg done convert hicolor/16x16/apps/icon.png hicolor/22x22/apps/icon.png hicolor/24x24/apps/icon.png hicolor/32x32/apps/icon.png hicolor/64x64/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png -colors 256 ../icon.ico +png2icns ../../cmake_builder/linphone_package/macos/linphone.icns hicolor/16x16/apps/icon.png hicolor/32x32/apps/icon.png hicolor/128x128/apps/icon.png hicolor/256x256/apps/icon.png diff --git a/linphone-app/assets/images/chat_room_custom.svg b/linphone-app/assets/images/chat_room_custom.svg index 408e19619..a9d140c6d 100644 --- a/linphone-app/assets/images/chat_room_custom.svg +++ b/linphone-app/assets/images/chat_room_custom.svg @@ -3,16 +3,19 @@ width="80" height="80" viewBox="0 0 80 80" + fill="none" version="1.1" - id="svg21" + id="svg8" sodipodi:docname="chat_room_custom.svg" - inkscape:version="1.1 (c68e22c387, 2021-05-23)" + inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + - - - - - + inkscape:current-layer="svg8" /> - - - - - - - - - - + id="g832" + transform="matrix(0.67345025,0,0,0.67345025,9.9999997,20)"> + + + diff --git a/linphone-app/assets/images/conference_merge_custom.svg b/linphone-app/assets/images/conference_merge_custom.svg new file mode 100644 index 000000000..8f195ab4b --- /dev/null +++ b/linphone-app/assets/images/conference_merge_custom.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + diff --git a/linphone-app/assets/images/draft_custom.svg b/linphone-app/assets/images/draft_custom.svg deleted file mode 100644 index 069924dae..000000000 --- a/linphone-app/assets/images/draft_custom.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - diff --git a/linphone-app/assets/languages/da.ts b/linphone-app/assets/languages/da.ts index 9aa50fa99..ef140d3c4 100644 --- a/linphone-app/assets/languages/da.ts +++ b/linphone-app/assets/languages/da.ts @@ -569,6 +569,11 @@ Server url ikke konfigureret. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/de.ts b/linphone-app/assets/languages/de.ts index 695bad434..978ccf4a7 100644 --- a/linphone-app/assets/languages/de.ts +++ b/linphone-app/assets/languages/de.ts @@ -569,6 +569,11 @@ Server URL ist nicht konfiguriert. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/en.ts b/linphone-app/assets/languages/en.ts index 80206faa0..5f69dff3a 100644 --- a/linphone-app/assets/languages/en.ts +++ b/linphone-app/assets/languages/en.ts @@ -569,6 +569,11 @@ Server URL not configured. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. Do you really want do cancel this meeting? + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + Meeting has been cancelled + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/es.ts b/linphone-app/assets/languages/es.ts index 659fa342d..039bd89a1 100644 --- a/linphone-app/assets/languages/es.ts +++ b/linphone-app/assets/languages/es.ts @@ -569,6 +569,11 @@ URL del servidor no configurada. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/fr_FR.ts b/linphone-app/assets/languages/fr_FR.ts index 64d39b8da..1360155b1 100644 --- a/linphone-app/assets/languages/fr_FR.ts +++ b/linphone-app/assets/languages/fr_FR.ts @@ -569,6 +569,11 @@ URL du serveur non configurée. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. Voulez-vous supprimer cette réunion ? + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + La réunion a été annulée + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/hu.ts b/linphone-app/assets/languages/hu.ts index 9a20f7ba0..ce392ee15 100644 --- a/linphone-app/assets/languages/hu.ts +++ b/linphone-app/assets/languages/hu.ts @@ -568,6 +568,11 @@ A kiszolgáló URL-je nincs konfigurálva. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/it.ts b/linphone-app/assets/languages/it.ts index 36fe51dec..c4d768163 100644 --- a/linphone-app/assets/languages/it.ts +++ b/linphone-app/assets/languages/it.ts @@ -569,6 +569,11 @@ URL del server non configurato. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/ja.ts b/linphone-app/assets/languages/ja.ts index d8bce574f..557a37a20 100644 --- a/linphone-app/assets/languages/ja.ts +++ b/linphone-app/assets/languages/ja.ts @@ -568,6 +568,11 @@ 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/lt.ts b/linphone-app/assets/languages/lt.ts index 360c55639..29cb489f1 100644 --- a/linphone-app/assets/languages/lt.ts +++ b/linphone-app/assets/languages/lt.ts @@ -570,6 +570,11 @@ Nesukonfigūruotas serverio url. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/pt_BR.ts b/linphone-app/assets/languages/pt_BR.ts index 96a09ec62..aaced721e 100644 --- a/linphone-app/assets/languages/pt_BR.ts +++ b/linphone-app/assets/languages/pt_BR.ts @@ -569,6 +569,11 @@ URL do servidor não configurado. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/ru.ts b/linphone-app/assets/languages/ru.ts index 2509e66ac..770bd2d14 100644 --- a/linphone-app/assets/languages/ru.ts +++ b/linphone-app/assets/languages/ru.ts @@ -570,6 +570,11 @@ 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/sv.ts b/linphone-app/assets/languages/sv.ts index 0f5b48209..da6160940 100644 --- a/linphone-app/assets/languages/sv.ts +++ b/linphone-app/assets/languages/sv.ts @@ -569,6 +569,11 @@ Serverwebbadressen är inte konfigurerad. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/tr.ts b/linphone-app/assets/languages/tr.ts index d4ab8547c..9b6612885 100644 --- a/linphone-app/assets/languages/tr.ts +++ b/linphone-app/assets/languages/tr.ts @@ -568,6 +568,11 @@ Sunucu url'si yapılandırılmadı. 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/uk.ts b/linphone-app/assets/languages/uk.ts index 603506eca..5f7d0b467 100644 --- a/linphone-app/assets/languages/uk.ts +++ b/linphone-app/assets/languages/uk.ts @@ -570,6 +570,11 @@ 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/assets/languages/zh_CN.ts b/linphone-app/assets/languages/zh_CN.ts index faa1aa2d5..f715c304e 100644 --- a/linphone-app/assets/languages/zh_CN.ts +++ b/linphone-app/assets/languages/zh_CN.ts @@ -568,6 +568,11 @@ 'Do you really want do cancel this meeting?' : Warning message to confirm the cancellation of a meeting. + + icsCancelledMeetingInvite + 'Meeting has been cancelled' : ICS Title for cancelled meetings + + ChatConferenceInvitationMessage diff --git a/linphone-app/resources.qrc b/linphone-app/resources.qrc index 41e57a845..a5ff5052e 100644 --- a/linphone-app/resources.qrc +++ b/linphone-app/resources.qrc @@ -62,6 +62,7 @@ assets/images/conference_audio_only_custom.svg assets/images/conference_layout_grid_custom.svg assets/images/conference_layout_active_speaker_custom.svg + assets/images/conference_merge_custom.svg assets/images/contact_add_custom.svg assets/images/contact_card_photo_custom.svg assets/images/contact_custom.svg @@ -77,7 +78,6 @@ assets/images/declined_outgoing_call_custom.svg assets/images/delete_custom.svg assets/images/download_custom.svg - assets/images/draft_custom.svg assets/images/drop_down_custom.svg assets/images/edit_custom.svg assets/images/ended_call_custom.svg diff --git a/linphone-app/src/app/App.cpp b/linphone-app/src/app/App.cpp index 4dd083b69..2d6fd820a 100644 --- a/linphone-app/src/app/App.cpp +++ b/linphone-app/src/app/App.cpp @@ -153,7 +153,7 @@ static inline bool installLocale (App &app, QTranslator &translator, const QLoca } static inline string getConfigPathIfExists (const QCommandLineParser &parser) { - QString filePath = parser.value("config"); + QString filePath = parser.isSet("config") ? parser.value("config") : ""; string configPath; if(!QUrl(filePath).isRelative()){ configPath = Utils::appStringToCoreString(FileDownloader::synchronousDownload(filePath, Utils::coreStringToAppString(Paths::getConfigDirPath(false)), true)); @@ -217,12 +217,21 @@ App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::U } bctbx_set_default_encoding(Constants::LinphoneLocaleEncoding);// Use UTF-8 for internals. Linphone uses UTF-8 so there will be no loss on data with less precise encodings. Qt will do the rest. + createParser(); + mParser->parse(this->arguments()); +// Get configuration for translators + shared_ptr config = Utils::getConfigIfExists (QString::fromStdString(getConfigPathIfExists(*mParser))); + + // Init locale. + mTranslator = new DefaultTranslator(this); + mDefaultTranslator = new DefaultTranslator(this); + initLocale(config); + Logger::init(config); + + createParser();// Recreate parser in order to use translations from config. mParser->process(*this); - // Initialize logger. - shared_ptr config = Utils::getConfigIfExists (QString::fromStdString(getConfigPathIfExists(*mParser))); - Logger::init(config); if (mParser->isSet("verbose")) Logger::getInstance()->setVerbose(true); @@ -230,11 +239,6 @@ App::App (int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::U for (const auto &locale : QDir(Constants::LanguagePath).entryList()) mAvailableLocales << QLocale(locale); - // Init locale. - mTranslator = new DefaultTranslator(this); - mDefaultTranslator = new DefaultTranslator(this); - initLocale(config); - if (mParser->isSet("help")) { mParser->showHelp(); } diff --git a/linphone-app/src/app/proxyModel/ProxyModel.cpp b/linphone-app/src/app/proxyModel/ProxyModel.cpp index 9734c7ff2..d47472a0c 100644 --- a/linphone-app/src/app/proxyModel/ProxyModel.cpp +++ b/linphone-app/src/app/proxyModel/ProxyModel.cpp @@ -35,6 +35,19 @@ ProxyModel::ProxyModel (QAbstractItemModel * model, const int& defaultFilterMode sort(0, Qt::DescendingOrder); } +ProxyModel::~ProxyModel(){ + if(mDeleteSourceModel) + deleteSourceModel(); +} + +void ProxyModel::deleteSourceModel(){ + auto oldSourceModel = sourceModel(); + if(oldSourceModel) { + oldSourceModel->deleteLater(); + setSourceModel(nullptr); + } +} + int ProxyModel::getFilterMode () const { return mFilterMode; } diff --git a/linphone-app/src/app/proxyModel/ProxyModel.hpp b/linphone-app/src/app/proxyModel/ProxyModel.hpp index d85e950b8..a85bb45bb 100644 --- a/linphone-app/src/app/proxyModel/ProxyModel.hpp +++ b/linphone-app/src/app/proxyModel/ProxyModel.hpp @@ -35,6 +35,9 @@ public: ProxyModel (QObject *parent = Q_NULLPTR); ProxyModel (QAbstractItemModel * list, const int& defaultFilterMode, QObject *parent = Q_NULLPTR); + virtual ~ProxyModel(); + + virtual void deleteSourceModel(); int getFilterMode () const; void setFilterMode (int filterMode); @@ -56,6 +59,8 @@ protected: bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override; bool lessThan (const QModelIndex &left, const QModelIndex &right) const override; + bool mDeleteSourceModel = false; + private: int mFilterMode; }; diff --git a/linphone-app/src/app/proxyModel/SortFilterProxyModel.cpp b/linphone-app/src/app/proxyModel/SortFilterProxyModel.cpp index f60c5149f..cc4da2f43 100644 --- a/linphone-app/src/app/proxyModel/SortFilterProxyModel.cpp +++ b/linphone-app/src/app/proxyModel/SortFilterProxyModel.cpp @@ -26,6 +26,19 @@ SortFilterProxyModel::SortFilterProxyModel(QObject * parent) : QSortFilterProxyM connect(this, &SortFilterProxyModel::rowsRemoved, this, &SortFilterProxyModel::countChanged); } +SortFilterProxyModel::~SortFilterProxyModel(){ + if(mDeleteSourceModel) + deleteSourceModel(); +} + +void SortFilterProxyModel::deleteSourceModel(){ + auto oldSourceModel = sourceModel(); + if(oldSourceModel) { + oldSourceModel->deleteLater(); + setSourceModel(nullptr); + } +} + int SortFilterProxyModel::getCount() const{ return rowCount(); } diff --git a/linphone-app/src/app/proxyModel/SortFilterProxyModel.hpp b/linphone-app/src/app/proxyModel/SortFilterProxyModel.hpp index 518593faa..e0df17682 100644 --- a/linphone-app/src/app/proxyModel/SortFilterProxyModel.hpp +++ b/linphone-app/src/app/proxyModel/SortFilterProxyModel.hpp @@ -30,6 +30,8 @@ public: Q_PROPERTY(int filterType READ getFilterType WRITE setFilterType NOTIFY filterTypeChanged) SortFilterProxyModel(QObject * parent = nullptr); + virtual ~SortFilterProxyModel(); + virtual void deleteSourceModel(); virtual int getCount() const; virtual int getFilterType () const; @@ -47,6 +49,7 @@ signals: protected: int mFilterType; + bool mDeleteSourceModel = false; }; #endif diff --git a/linphone-app/src/components/call/CallModel.cpp b/linphone-app/src/components/call/CallModel.cpp index e8a1a8623..63225c720 100644 --- a/linphone-app/src/components/call/CallModel.cpp +++ b/linphone-app/src/components/call/CallModel.cpp @@ -116,8 +116,10 @@ CallModel::CallModel (shared_ptr call){ if(mCall) { mRemoteAddress = mCall->getRemoteAddress()->clone(); - if(mCall->getConference()) + if(mCall->getConference()) { mConferenceModel = ConferenceModel::create(mCall->getConference()); + connect(mConferenceModel.get(), &ConferenceModel::participantAdminStatusChanged, this, &CallModel::onParticipantAdminStatusChanged); + } auto conferenceInfo = CoreManager::getInstance()->getCore()->findConferenceInformationFromUri(getConferenceAddress()); if( conferenceInfo ){ mConferenceInfoModel = ConferenceInfoModel::create(conferenceInfo); @@ -239,7 +241,8 @@ ConferenceInfoModel * CallModel::getConferenceInfoModel(){ QSharedPointer CallModel::getConferenceSharedModel(){ if(mCall->getConference() && !mConferenceModel){ - mConferenceModel = ConferenceModel::create(mCall->getConference()); + mConferenceModel = ConferenceModel::create(mCall->getConference()); + connect(mConferenceModel.get(), &ConferenceModel::participantAdminStatusChanged, this, &CallModel::onParticipantAdminStatusChanged); emit conferenceModelChanged(); } return mConferenceModel; @@ -247,7 +250,24 @@ QSharedPointer CallModel::getConferenceSharedModel(){ bool CallModel::isConference () const{ // Check status to avoid crash when requesting a conference on an ended call. - return mCall && (Utils::coreStringToAppString(mCall->getRemoteAddress()->asString()).toLower().contains("conf-id") || (getStatus() != CallStatusEnded && mCall->getConference() != nullptr)); + bool isConf = false; + if(mCall){ + // Do not call getConference on Ended status. + isConf = (getStatus() != CallStatusEnded && mCall->getConference() != nullptr) || mConferenceInfoModel != nullptr; + + if(!isConf){// Check special cases for Linphone. Having conf-id for a conference URI is not standard. + auto remoteAddress = mCall->getRemoteAddress(); + if( remoteAddress->getDomain() == Constants::LinphoneDomain){ + isConf = remoteAddress->hasUriParam("conf-id"); + } + } + } + + return isConf; +} + +bool CallModel::isOneToOne() const{ + return !isConference(); } // ----------------------------------------------------------------------------- @@ -972,6 +992,12 @@ void CallModel::onChatRoomInitialized(int state){ emit chatRoomModelChanged(); } +void CallModel::onParticipantAdminStatusChanged(const std::shared_ptr & participant){ + if(mConferenceModel && participant == mConferenceModel->getConference()->getMe()) { + emit meAdminChanged(); + } +} + void CallModel::setRemoteDisplayName(const std::string& name){ mRemoteAddress->setDisplayName(name); if(mCall) { diff --git a/linphone-app/src/components/call/CallModel.hpp b/linphone-app/src/components/call/CallModel.hpp index e11de11de..1d0c4f53f 100644 --- a/linphone-app/src/components/call/CallModel.hpp +++ b/linphone-app/src/components/call/CallModel.hpp @@ -55,7 +55,9 @@ class CallModel : public QObject { Q_PROPERTY(bool isOutgoing READ isOutgoing CONSTANT) Q_PROPERTY(bool isInConference READ isInConference NOTIFY isInConferenceChanged) - Q_PROPERTY(bool isConference READ isConference CONSTANT) + Q_PROPERTY(bool isConference READ isConference NOTIFY conferenceInfoModelChanged) + Q_PROPERTY(bool isOneToOne READ isOneToOne NOTIFY conferenceInfoModelChanged) + Q_PROPERTY(int duration READ getDuration CONSTANT) // Constants but called with a timer in qml. Q_PROPERTY(float quality READ getQuality CONSTANT) @@ -145,6 +147,7 @@ public: return mIsInConference; } bool isConference () const; + bool isOneToOne() const; void setRecordFile (const std::shared_ptr &callParams); static void setRecordFile (const std::shared_ptr &callParams, const QString &to); @@ -205,8 +208,10 @@ public slots: void endCall(); void onRemoteRecording(const std::shared_ptr & call, bool recording); void onChatRoomInitialized(int state); + void onParticipantAdminStatusChanged(const std::shared_ptr & participant); signals: + void meAdminChanged(); void callErrorChanged (const QString &callError); void callIdChanged(); void isInConferenceChanged (bool status); diff --git a/linphone-app/src/components/calls/CallsListModel.cpp b/linphone-app/src/components/calls/CallsListModel.cpp index e2c50ca14..4ab22c46b 100644 --- a/linphone-app/src/components/calls/CallsListModel.cpp +++ b/linphone-app/src/components/calls/CallsListModel.cpp @@ -69,6 +69,7 @@ CallsListModel::CallsListModel (QObject *parent) : ProxyListModel(parent) { mCoreHandlers.get(), &CoreHandlers::callStateChanged, this, &CallsListModel::handleCallStateChanged ); + connect(this, &CallsListModel::countChanged, this, &CallsListModel::canMergeCallsChanged); } CallModel *CallsListModel::findCallModelFromPeerAddress (const QString &peerAddress) const { @@ -343,7 +344,7 @@ QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& se initializer->setAdminsData(admins); ChatRoomInitializer::start(initializer); } - timeline = timelineList->getTimeline(chatRoom, ChatRoomModel::isTerminated(chatRoom)); + timeline = timelineList->getTimeline(chatRoom, true); }else{ if(admins.size() > 0){ ChatRoomInitializer::create(chatRoom)->setAdmins(admins); @@ -354,9 +355,7 @@ QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& se CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = false; result["chatRoomModel"] = QVariant::fromValue(timeline->getChatRoomModel()); if(selectAfterCreation) {// The timeline here will not receive the first creation event. Set Selected if needed - QTimer::singleShot(200, [timeline](){// Delay process in order to let GUI time for Timeline building/linking before doing actions - timeline->setSelected(true); - }); + timeline->delaySelected(); } } } @@ -368,19 +367,106 @@ QVariantMap CallsListModel::createChatRoom(const QString& subject, const int& se } void CallsListModel::prepareConferenceCall(ConferenceInfoModel * model){ - auto app = App::getInstance(); - app->smartShowWindow(app->getCallsWindow()); - emit callConferenceAsked(model); + if(model->getConferenceInfoState() != LinphoneEnums::ConferenceInfoStateCancelled) { + auto app = App::getInstance(); + app->smartShowWindow(app->getCallsWindow()); + emit callConferenceAsked(model); + } } + int CallsListModel::addAllToConference(){ return CoreManager::getInstance()->getCore()->addAllToConference(); } + +void CallsListModel::mergeAll(){ + auto core = CoreManager::getInstance()->getCore(); + auto currentCalls = CoreManager::getInstance()->getCore()->getCalls(); + shared_ptr conference = core->getConference(); + + // Search a managable conference from calls + if(!conference){ + for(auto call : currentCalls){ + auto dbConference = call->getConference(); + if(dbConference && dbConference->getMe()->isAdmin()){ + conference = dbConference; + break; + } + } + } + + auto currentCall = CoreManager::getInstance()->getCore()->getCurrentCall(); + bool enablingVideo = false; + if( currentCall ) + enablingVideo = currentCall->getCurrentParams()->videoEnabled(); + if(!conference){ + auto parameters = core->createConferenceParams(conference); + + if(!CoreManager::getInstance()->getSettingsModel()->getVideoConferenceEnabled()) { + parameters->enableVideo(false); + parameters->setConferenceFactoryAddress(nullptr);// Do a local conference + parameters->setSubject("Local meeting"); + }else{ + parameters->enableVideo(enablingVideo); + parameters->setSubject("Meeting"); + } + conference = core->createConferenceWithParams(parameters); + } + + list> allLinphoneAddresses; + list> newCalls; + list> runningCallsToAdd; + + for(auto call : currentCalls){ + if(!call->getConference()){ + runningCallsToAdd.push_back(call); + } + } + +// 1) Add running calls + if( runningCallsToAdd.size() > 0){ + conference->addParticipants(runningCallsToAdd); + } + /* +// 2) Put in pause and remove all calls that are not in the conference list + for(const auto &call : CoreManager::getInstance()->getCore()->getCalls()){ + const std::string callAddress = call->getRemoteAddress()->asStringUriOnly(); + auto address = allLinphoneAddresses.begin(); + while(address != allLinphoneAddresses.end() && (*address)->asStringUriOnly() != callAddress) + ++address; + if(address == allLinphoneAddresses.end()){// Not in conference list : put in pause and remove it from conference if it's the case + if( call->getParams()->getLocalConferenceMode() ){// Remove conference if it is not yet requested + CoreManager::getInstance()->getCore()->removeFromConference(call); + }else + call->pause(); + } + }*/ +} // ----------------------------------------------------------------------------- int CallsListModel::getRunningCallsNumber () const { return CoreManager::getInstance()->getCore()->getCallsNb(); } +bool CallsListModel::canMergeCalls()const{ + auto calls = CoreManager::getInstance()->getCore()->getCalls(); + + bool mergableConference = false; + int mergableCalls = 0; + bool mergable = false; + for(auto itCall = calls.begin(); !mergable && itCall != calls.end() ; ++itCall ) { + auto conference = (*itCall)->getConference(); + if(conference){ + if( !mergableConference ) + mergableConference = (conference && conference->getMe()->isAdmin()); + }else{ + ++mergableCalls; + } + mergable = (mergableConference && mergableCalls>0) // A call can be merged into the conference + || mergableCalls>1;// 2 calls can be merged + } + return mergable; +} + void CallsListModel::terminateAllCalls () const { CoreManager::getInstance()->getCore()->terminateAllCalls(); } @@ -479,6 +565,8 @@ void CallsListModel::addCall (const shared_ptr &call) { qInfo() << QStringLiteral("Add call:") << callModel->getFullLocalAddress() << callModel->getFullPeerAddress(); App::getInstance()->getEngine()->setObjectOwnership(callModel.get(), QQmlEngine::CppOwnership); + connect(callModel.get(), &CallModel::meAdminChanged, this, &CallsListModel::canMergeCallsChanged); + add(callModel); emit layoutChanged(); @@ -511,7 +599,7 @@ void CallsListModel::addDummyCall () { int id = findCallIndex(mList, *callModel); emit dataChanged(index(id, 0), index(id, 0)); }); - + connect(callModel.get(), &CallModel::meAdminChanged, this, &CallsListModel::canMergeCallsChanged); add(callModel); emit layoutChanged(); diff --git a/linphone-app/src/components/calls/CallsListModel.hpp b/linphone-app/src/components/calls/CallsListModel.hpp index c7ed56ce1..1c774c039 100644 --- a/linphone-app/src/components/calls/CallsListModel.hpp +++ b/linphone-app/src/components/calls/CallsListModel.hpp @@ -37,6 +37,9 @@ class CallsListModel : public ProxyListModel { Q_OBJECT public: + Q_PROPERTY(bool canMergeCalls READ canMergeCalls NOTIFY canMergeCallsChanged) + + CallsListModel (QObject *parent = Q_NULLPTR); CallModel *findCallModelFromPeerAddress (const QString &peerAddress) const; @@ -57,9 +60,11 @@ public: Q_INVOKABLE void prepareConferenceCall(ConferenceInfoModel * model); Q_INVOKABLE int addAllToConference(); + Q_INVOKABLE void mergeAll(); Q_INVOKABLE int getRunningCallsNumber () const; + bool canMergeCalls()const; Q_INVOKABLE void terminateAllCalls () const; Q_INVOKABLE void terminateCall (const QString& sipAddress) const; @@ -73,6 +78,7 @@ signals: void callConferenceAsked(ConferenceInfoModel * conferenceInfoModel); void callMissed (CallModel *callModel); + void canMergeCallsChanged(); private: diff --git a/linphone-app/src/components/camera/Camera.cpp b/linphone-app/src/components/camera/Camera.cpp index 5f58d7046..bacbd5afd 100644 --- a/linphone-app/src/components/camera/Camera.cpp +++ b/linphone-app/src/components/camera/Camera.cpp @@ -44,6 +44,7 @@ int Camera::mPreviewCounter; // ============================================================================= Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { + qDebug() << "[Camera] Camera constructor" << this; updateWindowIdLocation(); setTextureFollowsItemSize(true); // The fbo content must be y-mirrored because the ms rendering is y-inverted. @@ -66,9 +67,11 @@ Camera::Camera (QQuickItem *parent) : QQuickFramebufferObject(parent) { Camera::~Camera(){ qDebug() << "[Camera] Camera destructor" << this; + mRefreshTimer->stop(); + if(mIsPreview) deactivatePreview(); - setWindowIdLocation(None); + setWindowIdLocation(None);// We need to remove the Qt Buffer from SDK ot avoid to reuse it. } void Camera::resetWindowId() const{ @@ -157,8 +160,6 @@ void Camera::removeParticipantDeviceModel(){ } QQuickFramebufferObject::Renderer *Camera::createRenderer () const { - resetWindowId(); - QQuickFramebufferObject::Renderer * renderer = NULL; if(mWindowIdLocation == CorePreview){ qDebug() << "[Camera] Setting Camera to Preview"; diff --git a/linphone-app/src/components/chat-room/ChatRoomInitializer.cpp b/linphone-app/src/components/chat-room/ChatRoomInitializer.cpp index 8139f3fc3..97d5c682e 100644 --- a/linphone-app/src/components/chat-room/ChatRoomInitializer.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomInitializer.cpp @@ -83,7 +83,7 @@ void ChatRoomInitializer::setAdmins(QList< std::shared_ptr> a void ChatRoomInitializer::start(QSharedPointer initializer){ QObject * context = new QObject(); - QObject::connect(initializer.get(), &ChatRoomInitializer::finished, context, [context, initializer](int state){ + QObject::connect(initializer.get(), &ChatRoomInitializer::finished, context, [context, initializer](LinphoneEnums::ChatRoomState state){ qDebug() << "[ChatRoomInitializer] initialized"; context->deleteLater();// This will destroy context and initializer }); @@ -93,7 +93,7 @@ void ChatRoomInitializer::checkInitialization(){ if( mAdmins.size() > 0 && !mAdminsSet) return; - emit finished((int)mChatRoom->getState()); + emit finished(LinphoneEnums::fromLinphone(mChatRoom->getState())); } void ChatRoomInitializer::onConferenceJoined(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog) { diff --git a/linphone-app/src/components/chat-room/ChatRoomInitializer.hpp b/linphone-app/src/components/chat-room/ChatRoomInitializer.hpp index 3e2efd470..d7852fece 100644 --- a/linphone-app/src/components/chat-room/ChatRoomInitializer.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomInitializer.hpp @@ -24,6 +24,7 @@ #include #include "ChatRoomInitializer.hpp" +#include "utils/LinphoneEnums.hpp" #include @@ -54,7 +55,7 @@ public: virtual void onStateChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState); signals: - void finished(int state); // this signal is emit before deletion and give the current linphone::ChatRoom:State of the chat room. + void finished(LinphoneEnums::ChatRoomState state); // this signal is emit before deletion and give the current linphone::ChatRoom:State of the chat room. private: void connectTo(ChatRoomListener * listener); diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.cpp b/linphone-app/src/components/chat-room/ChatRoomModel.cpp index 28a7ace0b..02a52440b 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.cpp @@ -107,7 +107,7 @@ void ChatRoomModel::connectTo(ChatRoomListener * listener){ } // ----------------------------------------------------------------------------- -QSharedPointer ChatRoomModel::create(std::shared_ptr chatRoom, const std::list>& callLogs){ +QSharedPointer ChatRoomModel::create(const std::shared_ptr& chatRoom, const std::list>& callLogs){ QSharedPointer model = QSharedPointer::create(chatRoom, callLogs); if(model){ model->mSelf = model; @@ -117,7 +117,7 @@ QSharedPointer ChatRoomModel::create(std::shared_ptr chatRoom, const std::list>& callLogs, QObject * parent) : ProxyListModel(parent){ +ChatRoomModel::ChatRoomModel (const std::shared_ptr& chatRoom, const std::list>& callLogs, QObject * parent) : ProxyListModel(parent){ App::getInstance()->getEngine()->setObjectOwnership(this, QQmlEngine::CppOwnership);// Avoid QML to destroy it when passing by Q_INVOKABLE CoreManager *coreManager = CoreManager::getInstance(); mCoreHandlers = coreManager->getHandlers(); @@ -149,6 +149,7 @@ ChatRoomModel::ChatRoomModel (std::shared_ptr chatRoom, cons QObject::connect(coreManager->getContactsListModel(), &ContactsListModel::contactUpdated, this, &ChatRoomModel::avatarChanged); connect(this, &ChatRoomModel::fullPeerAddressChanged, this, &ChatRoomModel::usernameChanged); + connect(this, &ChatRoomModel::stateChanged, this, &ChatRoomModel::updatingChanged); if(mChatRoom){ mParticipantListModel = QSharedPointer::create(this); @@ -311,11 +312,11 @@ QString ChatRoomModel::getLocalAddress () const { } QString ChatRoomModel::getFullPeerAddress () const { - return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asString()) : ""; + return mChatRoom && mChatRoom->getPeerAddress() ? Utils::coreStringToAppString(mChatRoom->getPeerAddress()->asString()) : ""; } QString ChatRoomModel::getFullLocalAddress () const { - return mChatRoom ? Utils::coreStringToAppString(mChatRoom->getLocalAddress()->asString()) : ""; + return mChatRoom && mChatRoom->getLocalAddress()? Utils::coreStringToAppString(mChatRoom->getLocalAddress()->asString()) : ""; } QString ChatRoomModel::getConferenceAddress () const { @@ -404,8 +405,8 @@ std::list> ChatRoomModel::getParticipants return participantList; } -int ChatRoomModel::getState() const { - return mChatRoom ? (int)mChatRoom->getState() : 0; +LinphoneEnums::ChatRoomState ChatRoomModel::getState() const { + return mChatRoom ? LinphoneEnums::fromLinphone(mChatRoom->getState()) : LinphoneEnums::ChatRoomStateNone; } bool ChatRoomModel::isReadOnly() const{ @@ -481,6 +482,10 @@ bool ChatRoomModel::isBasic() const{ return mChatRoom && mChatRoom->hasCapability((int)linphone::ChatRoomCapabilities::Basic); } +bool ChatRoomModel::isUpdating() const{ + return getState() == LinphoneEnums::ChatRoomStateCreationPending || getState() == LinphoneEnums::ChatRoomStateTerminationPending; +} + std::shared_ptr ChatRoomModel::getChatRoom(){ return mChatRoom; } @@ -523,14 +528,6 @@ int ChatRoomModel::getAllUnreadCount(){ return mUnreadMessagesCount + mMissedCallsCount; } -QString ChatRoomModel::getCachedText()const{ - return mCachedText; -} - -bool ChatRoomModel::hasDraft() const{ - return mHasDraft; -} - //------------------------------------------------------------------------------------------------ void ChatRoomModel::setSubject(QString& subject){ @@ -605,23 +602,6 @@ void ChatRoomModel::enableMarkAsRead(const bool& enable){ } } -bool ChatRoomModel::setCachedText(const QString& text){ - if(mCachedText != text){ - mCachedText = text; - emit cachedTextChanged(); - setHasDraft(!mCachedText.isEmpty()); - return true; - }else - return false; -} - -void ChatRoomModel::setHasDraft(const bool& cached){ - if(mHasDraft != cached){ - mHasDraft = cached; - emit hasDraftChanged(); - } -} - void ChatRoomModel::setReply(ChatMessageModel * model){ if(model != mReplyModel.get()){ if( model && model->getChatMessage() ) @@ -645,15 +625,14 @@ void ChatRoomModel::markAsToDelete(){ void ChatRoomModel::deleteChatRoom(){ qInfo() << "Deleting ChatRoom : " << getSubject() << ", address=" << getFullPeerAddress(); if(mChatRoom){ - mChatRoom->removeListener(mChatRoomListener); CoreManager::getInstance()->getCore()->deleteChatRoom(mChatRoom); } - emit chatRoomDeleted(); } void ChatRoomModel::leaveChatRoom (){ if(mChatRoom){ - mChatRoom->leave(); + if(!isReadOnly()) + mChatRoom->leave(); if( mChatRoom->getHistorySize() == 0 && mChatRoom->getHistoryEventsSize() == 0) deleteChatRoom(); } @@ -724,7 +703,6 @@ void ChatRoomModel::sendMessage (const QString &message) { if(recorder->haveVocalRecorder()) recorder->clearVocalRecorder(); CoreManager::getInstance()->getChatModel()->clear(); - setCachedText(""); } } @@ -744,8 +722,8 @@ void ChatRoomModel::forwardMessage(ChatMessageModel * model){ } // ----------------------------------------------------------------------------- -void ChatRoomModel::compose (const QString& text) { - if( setCachedText(text) && mChatRoom)// only send a compose if text has changed +void ChatRoomModel::compose () { + if( mChatRoom) mChatRoom->compose(); } @@ -880,23 +858,31 @@ int ChatRoomModel::loadTillMessage(ChatMessageModel * message){ }); // if not find, load more entries and find it in new entries. if( entry == mList.end()){ + mPostModelChangedEvents = false; + beginResetModel(); int newEntries = loadMoreEntries(); while( newEntries > 0){// no more new entries int entryCount = 0; entry = mList.begin(); - auto chatEventEntry = entry->objectCast(); + auto chatEventEntry = entry->objectCast(); while(entryCount < newEntries && (chatEventEntry->mType != ChatRoomModel::EntryType::MessageEntry || chatEventEntry.objectCast()->getChatMessage() != linphoneMessage) ){ ++entryCount; ++entry; + if( entry != mList.end()) + chatEventEntry = entry->objectCast(); } if( entryCount < newEntries){// We got it qDebug() << "Find message at " << entryCount << " after loading new entries"; + mPostModelChangedEvents = true; + endResetModel(); return entryCount; }else newEntries = loadMoreEntries();// continue } + mPostModelChangedEvents = true; + endResetModel(); }else{ int entryCount = entry - mList.begin(); qDebug() << "Find message at " << entryCount; @@ -1051,18 +1037,21 @@ int ChatRoomModel::loadMoreEntries(){ EntrySorterHelper::getLimitedSelection(&entries, prepareEntries, mLastEntriesStep, this); if(entries.size() >0){ - beginInsertRows(QModelIndex(), 0, entries.size()-1); + if(mPostModelChangedEvents) + beginInsertRows(QModelIndex(), 0, entries.size()-1); for(auto entry : entries) mList.prepend(entry); - endInsertRows(); + if(mPostModelChangedEvents) + endInsertRows(); //emit layoutChanged(); updateLastUpdateTime(); } newEntries = entries.size(); }while( newEntries>0 && currentRowCount == rowCount()); - currentRowCount = rowCount() - currentRowCount + 1; + currentRowCount = rowCount() - currentRowCount; setEntriesLoading(false); - emit moreEntriesLoaded(currentRowCount); + if(mPostModelChangedEvents) + emit moreEntriesLoaded(currentRowCount); return currentRowCount; } @@ -1337,6 +1326,11 @@ void ChatRoomModel::onParticipantAdminStatusChanged(const std::shared_ptr & chatRoom, linphone::ChatRoom::State newState){ updateLastUpdateTime(); emit stateChanged(getState()); + if(newState == linphone::ChatRoom::State::Deleted){ + mChatRoom->removeListener(mChatRoomListener); + mChatRoom = nullptr; + emit chatRoomDeleted(); + } } void ChatRoomModel::onSecurityEvent(const std::shared_ptr & chatRoom, const std::shared_ptr & eventLog){ diff --git a/linphone-app/src/components/chat-room/ChatRoomModel.hpp b/linphone-app/src/components/chat-room/ChatRoomModel.hpp index bcac30bd0..4c25d05e6 100644 --- a/linphone-app/src/components/chat-room/ChatRoomModel.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomModel.hpp @@ -25,6 +25,8 @@ #include "app/proxyModel/ProxyListModel.hpp" #include +#include "utils/LinphoneEnums.hpp" + // ============================================================================= // Fetch all N messages of a ChatRoom. // ============================================================================= @@ -73,13 +75,14 @@ public: Q_PROPERTY(bool isComposing READ getIsRemoteComposing NOTIFY isRemoteComposingChanged) Q_PROPERTY(QList composers READ getComposers NOTIFY isRemoteComposingChanged) Q_PROPERTY(bool isReadOnly READ isReadOnly NOTIFY isReadOnlyChanged) + Q_PROPERTY(bool updating READ isUpdating NOTIFY updatingChanged) Q_PROPERTY(QString sipAddress READ getFullPeerAddress NOTIFY fullPeerAddressChanged) Q_PROPERTY(QString sipAddressUriOnly READ getPeerAddress 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(int state READ getState NOTIFY stateChanged) + Q_PROPERTY(LinphoneEnums::ChatRoomState state READ getState NOTIFY stateChanged) Q_PROPERTY(long ephemeralLifetime READ getEphemeralLifetime WRITE setEphemeralLifetime NOTIFY ephemeralLifetimeChanged) Q_PROPERTY(bool ephemeralEnabled READ isEphemeralEnabled WRITE setEphemeralEnabled NOTIFY ephemeralEnabledChanged) @@ -92,12 +95,9 @@ public: Q_PROPERTY(bool entriesLoading READ isEntriesLoading WRITE setEntriesLoading NOTIFY entriesLoadingChanged) - Q_PROPERTY(QString cachedText READ getCachedText WRITE setCachedText NOTIFY cachedTextChanged) - Q_PROPERTY(bool hasDraft READ hasDraft WRITE setHasDraft NOTIFY hasDraftChanged) - - static QSharedPointer create(std::shared_ptr chatRoom, const std::list>& callLogs = std::list>()); - ChatRoomModel (std::shared_ptr chatRoom, const std::list>& callLogs = std::list>(), QObject * parent = nullptr); + static QSharedPointer create(const std::shared_ptr& chatRoom, const std::list>& callLogs = std::list>()); + ChatRoomModel (const std::shared_ptr& chatRoom, const std::list>& callLogs = std::list>(), QObject * parent = nullptr); ~ChatRoomModel (); @@ -119,7 +119,7 @@ public: QString getUsername () const; QString getAvatar () const; int getPresenceStatus() const; - int getState() const; + LinphoneEnums::ChatRoomState getState() const; bool isReadOnly() const; bool isEphemeralEnabled() const; long getEphemeralLifetime() const; @@ -138,14 +138,14 @@ public: bool getIsRemoteComposing () const; bool isEntriesLoading() const; bool isBasic() const; + bool isUpdating() const; + ParticipantListModel* getParticipantListModel() const; std::list> getParticipants(const bool& withMe = true) const; std::shared_ptr getChatRoom(); QList getComposers(); QString getParticipantAddress(); // return peerAddress if not secure else return the first participant SIP address. int getAllUnreadCount(); // Return unread messages and missed call. - QString getCachedText() const; - bool hasDraft() const; //---- Setters void setSubject(QString& subject); @@ -159,8 +159,6 @@ public: void setEphemeralEnabled(bool enabled); void setEphemeralLifetime(long lifetime); void enableMarkAsRead(const bool& enable); - bool setCachedText(const QString& text); // return true if cache changed - void setHasDraft(const bool& draft); void setReply(ChatMessageModel * model); ChatMessageModel * getReply()const; @@ -175,7 +173,7 @@ public: Q_INVOKABLE void updateParticipants(const QVariantList& participants); void sendMessage (const QString &message); Q_INVOKABLE void forwardMessage(ChatMessageModel * model); - void compose (const QString& text); + void compose (); Q_INVOKABLE void resetMessageCount (); void initEntries(); Q_INVOKABLE int loadMoreEntries(); // return new entries count @@ -280,8 +278,7 @@ signals: void markAsReadEnabledChanged(); void chatRoomDeleted();// Must be connected with DirectConnection mode void replyChanged(); - void cachedTextChanged(); - void hasDraftChanged(); + void updatingChanged(); // Chat Room listener callbacks @@ -314,8 +311,7 @@ private: QSharedPointer mReplyModel; QSharedPointer mUnreadMessageNotice; int mBindingCalls = 0; - QString mCachedText; // TODO : replace it by content to manage files/audio etc. - bool mHasDraft = false; + bool mPostModelChangedEvents = true; QWeakPointer mSelf; }; diff --git a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp index cc644c9bd..fd37071b3 100644 --- a/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp +++ b/linphone-app/src/components/chat-room/ChatRoomProxyModel.cpp @@ -36,6 +36,8 @@ using namespace std; +QString ChatRoomProxyModel::gCachedText; + // ============================================================================= ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel(parent) { @@ -55,6 +57,7 @@ ChatRoomProxyModel::ChatRoomProxyModel (QObject *parent) : QSortFilterProxyModel } ChatRoomProxyModel::~ChatRoomProxyModel(){ + setSourceModel(nullptr); setChatRoomModel(nullptr); // Do remove process like setting haveCall if is Call. } @@ -99,7 +102,8 @@ CREATE_PARENT_MODEL_FUNCTION(deleteChatRoom) void ChatRoomProxyModel::compose (const QString& text) { if (mChatRoomModel) - mChatRoomModel->compose(text); + mChatRoomModel->compose(); + gCachedText = text; } int ChatRoomProxyModel::getEntryTypeFilter () { @@ -239,6 +243,10 @@ QVariant ChatRoomProxyModel::getAt(int row){ return sourceModel()->data(sourceIndex); } +QString ChatRoomProxyModel::getCachedText() const{ + return gCachedText; +} + void ChatRoomProxyModel::setIsCall(const bool& isCall){ if(mIsCall != isCall) { if(mChatRoomModel){ @@ -328,7 +336,7 @@ void ChatRoomProxyModel::setChatRoomModel (ChatRoomModel *chatRoomModel){ }else{ if(mIsCall && mChatRoomModel) mChatRoomModel->removeBindingCall(); - mChatRoomModel = nullptr; + mChatRoomModel = nullptr; } } // ----------------------------------------------------------------------------- diff --git a/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp b/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp index 03758ec28..39a5dc675 100644 --- a/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp +++ b/linphone-app/src/components/chat-room/ChatRoomProxyModel.hpp @@ -40,6 +40,7 @@ class ChatRoomProxyModel : public QSortFilterProxyModel { Q_PROPERTY(QString fullLocalAddress READ getFullLocalAddress WRITE setFullLocalAddress NOTIFY fullLocalAddressChanged) Q_PROPERTY(ChatRoomModel *chatRoomModel READ getChatRoomModel WRITE setChatRoomModel NOTIFY chatRoomModelChanged) Q_PROPERTY(QList composers READ getComposers NOTIFY isRemoteComposingChanged) + Q_PROPERTY(QString cachedText READ getCachedText) Q_PROPERTY(QString filterText MEMBER mFilterText WRITE setFilterText NOTIFY filterTextChanged) Q_PROPERTY(bool markAsReadEnabled READ markAsReadEnabled WRITE enableMarkAsRead NOTIFY markAsReadEnabledChanged)// Focus is at end of the list. Used to reset message count if not at end @@ -119,6 +120,9 @@ private: void setChatRoomModel (ChatRoomModel *chatRoomModel); QList getComposers () const; + + QString getCachedText() const; + void reload (ChatRoomModel *chatRoomModel); void handleIsActiveChanged (QWindow *window); @@ -134,6 +138,7 @@ private: QString mLocalAddress; QString mFullPeerAddress; QString mFullLocalAddress; + static QString gCachedText; bool mMarkAsReadEnabled; bool mIsCall = false; diff --git a/linphone-app/src/components/conference/ConferenceListener.cpp b/linphone-app/src/components/conference/ConferenceListener.cpp index 32a5a55f1..19f65c159 100644 --- a/linphone-app/src/components/conference/ConferenceListener.cpp +++ b/linphone-app/src/components/conference/ConferenceListener.cpp @@ -41,6 +41,11 @@ ConferenceListener::~ConferenceListener(){ //----------------------------------------------------------------------------------------------------------------------- // LINPHONE LISTENERS //----------------------------------------------------------------------------------------------------------------------- +void ConferenceListener::onActiveSpeakerParticipantDevice(const std::shared_ptr & conference, const std::shared_ptr & participantDevice) { + qDebug() << "onActiveSpeakerParticipantDevice: " << participantDevice->getAddress()->asString().c_str(); + emit activeSpeakerParticipantDevice(participantDevice); +} + void ConferenceListener::onParticipantAdded(const std::shared_ptr & conference, const std::shared_ptr & participant){ qDebug() << "onParticipantAdded: " << participant->getAddress()->asString().c_str(); emit participantAdded(participant); diff --git a/linphone-app/src/components/conference/ConferenceListener.hpp b/linphone-app/src/components/conference/ConferenceListener.hpp index 7ad651fb3..8b80dbd47 100644 --- a/linphone-app/src/components/conference/ConferenceListener.hpp +++ b/linphone-app/src/components/conference/ConferenceListener.hpp @@ -35,6 +35,7 @@ public: virtual ~ConferenceListener(); // LINPHONE LISTENERS + virtual void onActiveSpeakerParticipantDevice(const std::shared_ptr & conference, const std::shared_ptr & participantDevice) override; virtual void onParticipantAdded(const std::shared_ptr & conference, const std::shared_ptr & participant) override; virtual void onParticipantRemoved(const std::shared_ptr & conference, const std::shared_ptr & participant) override; virtual void onParticipantAdminStatusChanged(const std::shared_ptr & conference, const std::shared_ptr & participant) override; @@ -50,6 +51,7 @@ public: //--------------------------------------------------------------------------- signals: + void activeSpeakerParticipantDevice(const std::shared_ptr & participantDevice); void participantAdded(const std::shared_ptr & participant); void participantRemoved(const std::shared_ptr & participant); void participantAdminStatusChanged(const std::shared_ptr & participant); @@ -61,6 +63,7 @@ signals: void participantDeviceIsSpeakingChanged(const std::shared_ptr & participantDevice, bool isSpeaking); void conferenceStateChanged(linphone::Conference::State newState); void subjectChanged(const std::string & subject); + }; diff --git a/linphone-app/src/components/conference/ConferenceModel.cpp b/linphone-app/src/components/conference/ConferenceModel.cpp index aad85a9ac..e09a7d510 100644 --- a/linphone-app/src/components/conference/ConferenceModel.cpp +++ b/linphone-app/src/components/conference/ConferenceModel.cpp @@ -37,6 +37,7 @@ #include "components/Components.hpp" void ConferenceModel::connectTo(ConferenceListener * listener){ + connect(listener, &ConferenceListener::activeSpeakerParticipantDevice, this, &ConferenceModel::onActiveSpeakerParticipantDevice); connect(listener, &ConferenceListener::participantAdded, this, &ConferenceModel::onParticipantAdded); connect(listener, &ConferenceListener::participantRemoved, this, &ConferenceModel::onParticipantRemoved); connect(listener, &ConferenceListener::participantAdminStatusChanged, this, &ConferenceModel::onParticipantAdminStatusChanged); @@ -140,6 +141,9 @@ void ConferenceModel::setIsReady(bool state){ //----------------------------------------------------------------------------------------------------------------------- // LINPHONE LISTENERS //----------------------------------------------------------------------------------------------------------------------- +void ConferenceModel::onActiveSpeakerParticipantDevice(const std::shared_ptr & participantDevice){ + emit activeSpeakerParticipantDevice(participantDevice); +} void ConferenceModel::onParticipantAdded(const std::shared_ptr & participant){ qDebug() << "Added call, participant count: " << getParticipantList().size() << ". Me devices : " << mConference->getMe()->getDevices().size(); updateLocalParticipant(); diff --git a/linphone-app/src/components/conference/ConferenceModel.hpp b/linphone-app/src/components/conference/ConferenceModel.hpp index f5936b8ef..214681e2a 100644 --- a/linphone-app/src/components/conference/ConferenceModel.hpp +++ b/linphone-app/src/components/conference/ConferenceModel.hpp @@ -62,7 +62,7 @@ public: void setIsReady(bool state); - + virtual void onActiveSpeakerParticipantDevice(const std::shared_ptr & participantDevice); virtual void onParticipantAdded(const std::shared_ptr & participant); virtual void onParticipantRemoved(const std::shared_ptr & participant); virtual void onParticipantAdminStatusChanged(const std::shared_ptr & participant); @@ -77,6 +77,7 @@ public: //--------------------------------------------------------------------------- signals: + void activeSpeakerParticipantDevice(const std::shared_ptr & participantDevice); void localParticipantChanged(); void participantAdded(const std::shared_ptr & participant); void participantRemoved(const std::shared_ptr & participant); diff --git a/linphone-app/src/components/conference/ConferenceProxyModel.cpp b/linphone-app/src/components/conference/ConferenceProxyModel.cpp index a2a922a13..bf028b98b 100644 --- a/linphone-app/src/components/conference/ConferenceProxyModel.cpp +++ b/linphone-app/src/components/conference/ConferenceProxyModel.cpp @@ -38,6 +38,7 @@ using namespace std; ConferenceProxyModel::ConferenceProxyModel (QObject *parent) : SortFilterProxyModel(parent) { + mDeleteSourceModel = false; setSourceModel(CoreManager::getInstance()->getCallsListModel()); emit conferenceChanged(); diff --git a/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.cpp b/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.cpp index 1aa28f80c..b889ecf02 100644 --- a/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.cpp +++ b/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.cpp @@ -43,7 +43,7 @@ ConferenceInfoListModel::ConferenceInfoListModel (QObject *parent) : ProxyListMo auto conferenceInfos = coreManager->getCore()->getConferenceInformationList(); QList > items; for(auto conferenceInfo : conferenceInfos){ - auto item = build(conferenceInfo); + auto item = build(conferenceInfo, mBuildAll); if(item) items << item; } @@ -53,10 +53,10 @@ ConferenceInfoListModel::ConferenceInfoListModel (QObject *parent) : ProxyListMo // ----------------------------------------------------------------------------- -QSharedPointer ConferenceInfoListModel::build(const std::shared_ptr & conferenceInfo) const{ +QSharedPointer ConferenceInfoListModel::build(const std::shared_ptr & conferenceInfo, const bool& buildAll) const{ auto me = CoreManager::getInstance()->getCore()->getDefaultAccount()->getParams()->getIdentityAddress(); std::list> participants = conferenceInfo->getParticipants(); - bool haveMe = conferenceInfo->getOrganizer()->weakEqual(me); + bool haveMe = buildAll || conferenceInfo->getOrganizer()->weakEqual(me); if(!haveMe) haveMe = (std::find_if(participants.begin(), participants.end(), [me](const std::shared_ptr& address){ return me->weakEqual(address); @@ -70,7 +70,7 @@ QSharedPointer ConferenceInfoListModel::build(const std::sh } void ConferenceInfoListModel::add(const std::shared_ptr & conferenceInfo, const bool& sendEvents){ - auto item = build(conferenceInfo); + auto item = build(conferenceInfo, mBuildAll); if( item) ProxyListModel::add(item); } @@ -89,7 +89,7 @@ QVariant ConferenceInfoListModel::data (const QModelIndex &index, int role ) con if (role == Qt::DisplayRole) return QVariant::fromValue(mList[row].get()); else if (role == Qt::DisplayRole +1 ) - return QVariant::fromValue(mList[row].objectCast()->getDateTimeUtc().date()); + return QVariant::fromValue(mList[row].objectCast()->getDateTimeSystem().date()); return QVariant(); } diff --git a/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.hpp b/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.hpp index c7ee051f5..abf586002 100644 --- a/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.hpp +++ b/linphone-app/src/components/conferenceInfo/ConferenceInfoListModel.hpp @@ -37,7 +37,7 @@ class ConferenceInfoListModel : public ProxyListModel { public: ConferenceInfoListModel (QObject *parent = Q_NULLPTR); - QSharedPointer build(const std::shared_ptr & conferenceInfo) const; + QSharedPointer build(const std::shared_ptr & conferenceInfo, const bool& buildAll) const; void add(const std::shared_ptr & conferenceInfo, const bool& sendEvents = true); @@ -50,7 +50,8 @@ public slots: void onRemoved(bool byUser); signals: void filterTypeChanged(int filterType); - +private: + bool mBuildAll = true; // Short term design choice : display all. As of 5.2.0 SDK and on cancel, there are no more more links between conference info and current account. }; Q_DECLARE_METATYPE(ConferenceInfoListModel*) #endif diff --git a/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.cpp b/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.cpp index 583e8a855..2ee413582 100644 --- a/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.cpp +++ b/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.cpp @@ -104,6 +104,7 @@ ConferenceInfoModel::ConferenceInfoModel (QObject * parent) : QObject(parent){ connect(this, &ConferenceInfoModel::conferenceInfoChanged, this, &ConferenceInfoModel::isScheduledChanged); connect(this, &ConferenceInfoModel::conferenceInfoChanged, this, &ConferenceInfoModel::inviteModeChanged); connect(this, &ConferenceInfoModel::conferenceInfoChanged, this, &ConferenceInfoModel::conferenceInfoStateChanged); + connect(this, &ConferenceInfoModel::conferenceInfoChanged, this, &ConferenceInfoModel::conferenceSchedulerStateChanged); } // Callable from C++ @@ -124,6 +125,7 @@ ConferenceInfoModel::ConferenceInfoModel (std::shared_ptr ConferenceInfoModel::findConferenceInf //------------------------------------------------------------------------------------------------ - -QDateTime ConferenceInfoModel::getDateTimeUtc() const{ - return QDateTime::fromMSecsSinceEpoch(mConferenceInfo->getDateTime() * 1000).toUTC(); +//Note conferenceInfo->getDateTime uses system timezone and fromMSecsSinceEpoch need a UTC +QDateTime ConferenceInfoModel::getDateTimeSystem() const{ + QDateTime reference(QDateTime::fromMSecsSinceEpoch(mConferenceInfo->getDateTime() * 1000));// Get a reference for timezone offset computing + qint64 utcMs = (mConferenceInfo->getDateTime() - QTimeZone::systemTimeZone().offsetFromUtc(reference)) * 1000;// Remove system timezone offset to get UTC + return QDateTime::fromMSecsSinceEpoch(utcMs, QTimeZone::systemTimeZone()); // Return a System Timezone datetime based } -QDateTime ConferenceInfoModel::getDateTimeSystem() const{ - QDateTime utc = getDateTimeUtc(); - return utc.addSecs(QTimeZone::systemTimeZone().offsetFromUtc(utc)); +QDateTime ConferenceInfoModel::getDateTimeUtc() const{ + return getDateTimeSystem().toUTC(); } int ConferenceInfoModel::getDuration() const{ @@ -204,11 +207,29 @@ QVariantList ConferenceInfoModel::getParticipants() const{ } return addresses; } +QVariantList ConferenceInfoModel::getAllParticipants() const{ + QVariantList addresses = getParticipants(); + QString organizerAddress = QString::fromStdString(mConferenceInfo->getOrganizer()->asStringUriOnly()); + for(auto item : addresses){ + if( item.toMap()["address"] == organizerAddress) + return addresses; + } + QVariantMap participant; + participant["displayName"] = Utils::getDisplayName(mConferenceInfo->getOrganizer()); + participant["address"] = organizerAddress; + addresses << participant; + return addresses; +} + int ConferenceInfoModel::getParticipantCount()const{ return mConferenceInfo->getParticipants().size(); } +int ConferenceInfoModel::getAllParticipantCount()const{ + return getAllParticipants().size(); +} + TimeZoneModel* ConferenceInfoModel::getTimeZoneModel() const{ TimeZoneModel * model = new TimeZoneModel(mTimeZone); App::getInstance()->getEngine()->setObjectOwnership(model, QQmlEngine::JavaScriptOwnership); @@ -223,12 +244,18 @@ LinphoneEnums::ConferenceInfoState ConferenceInfoModel::getConferenceInfoState() return LinphoneEnums::fromLinphone(mConferenceInfo->getState()); } +LinphoneEnums::ConferenceSchedulerState ConferenceInfoModel::getConferenceSchedulerState() const{ + return LinphoneEnums::fromLinphone(mLastConferenceSchedulerState); +} + //------------------------------------------------------------------------------------------------ -// Convert into UTC with TimeZone and pass system timezone to conference info +// Datetime is in Custom (Locale/UTC/System). Convert into system timezone for conference info void ConferenceInfoModel::setDateTime(const QDateTime& dateTime){ - QDateTime utc = dateTime.addSecs( -mTimeZone.offsetFromUtc(dateTime)); - QDateTime system = utc.addSecs(QTimeZone::systemTimeZone().offsetFromUtc(utc)); - mConferenceInfo->setDateTime(system.toMSecsSinceEpoch() / 1000); + QDateTime system = dateTime.toTimeZone(QTimeZone::systemTimeZone());//System + int offset = QTimeZone::systemTimeZone().offsetFromUtc(system);//Get UTC offset in system coordinate + system = system.addSecs( offset - mTimeZone.offsetFromUtc(dateTime));// Delta on offsets + mConferenceInfo->setDateTime(system.toMSecsSinceEpoch() / 1000 + offset);// toMSecsSinceEpoch() is UTC, add system reference. + emit dateTimeChanged(); } @@ -254,6 +281,7 @@ void ConferenceInfoModel::setDescription(const QString& description){ void ConferenceInfoModel::setParticipants(ParticipantListModel * participants){ mConferenceInfo->setParticipants(participants->getParticipants()); + emit participantsChanged(); } void ConferenceInfoModel::setTimeZoneModel(TimeZoneModel * model){ @@ -314,23 +342,22 @@ void ConferenceInfoModel::createConference(const int& securityLevel) { CoreManager::getInstance()->getTimelineListModel()->mAutoSelectAfterCreation = false; shared_ptr core = CoreManager::getInstance()->getCore(); static std::shared_ptr conference; - qInfo() << "Conference creation of " << getSubject() << " at " << securityLevel << " security, organized by " << getOrganizer(); + qInfo() << "Conference creation of " << getSubject() << " at " << securityLevel << " security, organized by " << getOrganizer() << " for " << getDateTimeSystem().toString(); qInfo() << "Participants:"; for(auto p : mConferenceInfo->getParticipants()) qInfo() << "\t" << p->asString().c_str(); - mConferenceScheduler = ConferenceScheduler::create(); mConferenceScheduler->mSendInvite = mInviteMode; connect(mConferenceScheduler.get(), &ConferenceScheduler::invitationsSent, this, &ConferenceInfoModel::onInvitationsSent); - connect(mConferenceScheduler.get(), &ConferenceScheduler::stateChanged, this, &ConferenceInfoModel::onStateChanged); + connect(mConferenceScheduler.get(), &ConferenceScheduler::stateChanged, this, &ConferenceInfoModel::onConferenceSchedulerStateChanged); mConferenceScheduler->getConferenceScheduler()->setInfo(mConferenceInfo); } void ConferenceInfoModel::cancelConference(){ mConferenceScheduler = ConferenceScheduler::create(); connect(mConferenceScheduler.get(), &ConferenceScheduler::invitationsSent, this, &ConferenceInfoModel::onInvitationsSent); - connect(mConferenceScheduler.get(), &ConferenceScheduler::stateChanged, this, &ConferenceInfoModel::onStateChanged); + connect(mConferenceScheduler.get(), &ConferenceScheduler::stateChanged, this, &ConferenceInfoModel::onConferenceSchedulerStateChanged); mConferenceScheduler->getConferenceScheduler()->cancelConference(mConferenceInfo); } @@ -343,12 +370,14 @@ void ConferenceInfoModel::deleteConferenceInfo(){ //------------------------------------------------------------------------------------------------- -void ConferenceInfoModel::onStateChanged(linphone::ConferenceScheduler::State state){ - qDebug() << "ConferenceInfoModel::onStateChanged: " << (int) state; +void ConferenceInfoModel::onConferenceSchedulerStateChanged(linphone::ConferenceScheduler::State state){ + qDebug() << "ConferenceInfoModel::onConferenceSchedulerStateChanged: " << (int) state; + mLastConferenceSchedulerState = state; if( state == linphone::ConferenceScheduler::State::Ready) emit conferenceCreated(); else if( state == linphone::ConferenceScheduler::State::Error) emit conferenceCreationFailed(); + emit conferenceInfoChanged(); } void ConferenceInfoModel::onInvitationsSent(const std::list> & failedInvitations) { qDebug() << "ConferenceInfoModel::onInvitationsSent"; diff --git a/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.hpp b/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.hpp index 17087a240..7f2ec0e27 100644 --- a/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.hpp +++ b/linphone-app/src/components/conferenceInfo/ConferenceInfoModel.hpp @@ -49,7 +49,10 @@ public: Q_PROPERTY(QString uri READ getUri NOTIFY uriChanged) Q_PROPERTY(bool isScheduled READ isScheduled WRITE setIsScheduled NOTIFY isScheduledChanged) Q_PROPERTY(int inviteMode READ getInviteMode WRITE setInviteMode NOTIFY inviteModeChanged) + Q_PROPERTY(int participantCount READ getParticipantCount NOTIFY participantsChanged) + Q_PROPERTY(int allParticipantCount READ getAllParticipantCount NOTIFY participantsChanged) Q_PROPERTY(LinphoneEnums::ConferenceInfoState state READ getConferenceInfoState NOTIFY conferenceInfoStateChanged) + Q_PROPERTY(LinphoneEnums::ConferenceSchedulerState conferenceSchedulerState READ getConferenceSchedulerState NOTIFY conferenceSchedulerStateChanged) static QSharedPointer create(std::shared_ptr conferenceInfo); ConferenceInfoModel (QObject * parent = nullptr); @@ -72,10 +75,13 @@ public: bool isScheduled() const; int getInviteMode() const; Q_INVOKABLE QVariantList getParticipants() const; + Q_INVOKABLE QVariantList getAllParticipants() const; Q_INVOKABLE int getParticipantCount()const; + Q_INVOKABLE int getAllParticipantCount()const; Q_INVOKABLE TimeZoneModel* getTimeZoneModel() const; Q_INVOKABLE QString getIcalendarString() const; LinphoneEnums::ConferenceInfoState getConferenceInfoState() const; + LinphoneEnums::ConferenceSchedulerState getConferenceSchedulerState() const; void setDateTime(const QDateTime& dateTime); void setDuration(const int& duration); @@ -97,10 +103,9 @@ public: // SCHEDULER - virtual void onStateChanged(linphone::ConferenceScheduler::State state); + virtual void onConferenceSchedulerStateChanged(linphone::ConferenceScheduler::State state); virtual void onInvitationsSent(const std::list> & failedInvitations); - signals: void timeZoneModelChanged(); void dateTimeChanged(); @@ -113,6 +118,7 @@ signals: void isScheduledChanged(); void inviteModeChanged(); void conferenceInfoStateChanged(); + void conferenceSchedulerStateChanged(); void conferenceCreated(); void conferenceCreationFailed(); @@ -128,6 +134,7 @@ private: bool mIsScheduled = true; int mInviteMode = 0; bool mRemoveRequested = false;// true if user has request its deletion from DB + linphone::ConferenceScheduler::State mLastConferenceSchedulerState = linphone::ConferenceScheduler::State::Idle;// Workaround for missing getter in scheduler. }; Q_DECLARE_METATYPE(QSharedPointer) diff --git a/linphone-app/src/components/conferenceInfo/ConferenceInfoProxyModel.cpp b/linphone-app/src/components/conferenceInfo/ConferenceInfoProxyModel.cpp index 885b8c058..c3eb602ec 100644 --- a/linphone-app/src/components/conferenceInfo/ConferenceInfoProxyModel.cpp +++ b/linphone-app/src/components/conferenceInfo/ConferenceInfoProxyModel.cpp @@ -54,6 +54,8 @@ bool ConferenceInfoProxyModel::filterAcceptsRow (int sourceRow, const QModelInde QModelIndex index = listModel->index(sourceRow, 0, QModelIndex()); const ConferenceInfoModel* ics = sourceModel()->data(index).value(); if(ics){ + if(ics->getDuration() == 0) + return false; QDateTime currentDateTime = QDateTime::currentDateTime(); if( mFilterType == 0){ return ics->getEndDateTime() < currentDateTime; @@ -65,7 +67,7 @@ bool ConferenceInfoProxyModel::filterAcceptsRow (int sourceRow, const QModelInde return mFilterType == -1; } } - return true; + return false; } bool ConferenceInfoProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const { diff --git a/linphone-app/src/components/contact/ContactModel.cpp b/linphone-app/src/components/contact/ContactModel.cpp index ddfa6cc25..7d85c58d3 100644 --- a/linphone-app/src/components/contact/ContactModel.cpp +++ b/linphone-app/src/components/contact/ContactModel.cpp @@ -215,3 +215,7 @@ Presence::PresenceLevel ContactModel::getPresenceLevel () const { bool ContactModel::hasCapability(const LinphoneEnums::FriendCapability& capability){ return mLinphoneFriend->hasCapability(LinphoneEnums::toLinphone(capability)); } + +std::shared_ptr ContactModel::getFriend() const{ + return mLinphoneFriend; +} \ No newline at end of file diff --git a/linphone-app/src/components/contact/ContactModel.hpp b/linphone-app/src/components/contact/ContactModel.hpp index c59eb01dd..e482b8d88 100644 --- a/linphone-app/src/components/contact/ContactModel.hpp +++ b/linphone-app/src/components/contact/ContactModel.hpp @@ -57,6 +57,8 @@ public: Q_INVOKABLE VcardModel *cloneVcardModel () const; Presence::PresenceLevel getPresenceLevel () const; Q_INVOKABLE bool hasCapability(const LinphoneEnums::FriendCapability& capability); + + std::shared_ptr getFriend() const; signals: void contactUpdated (); diff --git a/linphone-app/src/components/contact/VcardModel.cpp b/linphone-app/src/components/contact/VcardModel.cpp index 9f44ba24e..f807d756e 100644 --- a/linphone-app/src/components/contact/VcardModel.cpp +++ b/linphone-app/src/components/contact/VcardModel.cpp @@ -460,9 +460,12 @@ QVariantList VcardModel::getUrls () const { return list; } -bool VcardModel::addUrl (const QString &url) { +bool VcardModel::addUrl (QString url) { CHECK_VCARD_IS_WRITABLE(this); - + QUrl urlParser(url); + if( urlParser.scheme() == ""){ + url = "https://"+url; + } shared_ptr belcard = mVcard->getVcard(); if (findBelCardValue(belcard->getURLs(), url)) return false; diff --git a/linphone-app/src/components/contact/VcardModel.hpp b/linphone-app/src/components/contact/VcardModel.hpp index bbf6f0ce4..b4828ee38 100644 --- a/linphone-app/src/components/contact/VcardModel.hpp +++ b/linphone-app/src/components/contact/VcardModel.hpp @@ -88,7 +88,7 @@ public: Q_INVOKABLE void removeEmail (const QString &email); Q_INVOKABLE bool updateEmail (const QString &oldEmail, const QString &email); - Q_INVOKABLE bool addUrl (const QString &url); + Q_INVOKABLE bool addUrl (QString url); Q_INVOKABLE void removeUrl (const QString &url); Q_INVOKABLE bool updateUrl (const QString &oldUrl, const QString &url); diff --git a/linphone-app/src/components/contacts/ContactsListModel.cpp b/linphone-app/src/components/contacts/ContactsListModel.cpp index c2bf043b0..efb6522e2 100644 --- a/linphone-app/src/components/contacts/ContactsListModel.cpp +++ b/linphone-app/src/components/contacts/ContactsListModel.cpp @@ -24,40 +24,26 @@ #include "components/contact/ContactModel.hpp" #include "components/contact/VcardModel.hpp" #include "components/core/CoreManager.hpp" +#include "components/friend/FriendListListener.hpp" #include "ContactsListModel.hpp" +// ============================================================================= +void ContactsListModel::connectTo(FriendListListener * listener){ + connect(listener, &FriendListListener::contactCreated, this, &ContactsListModel::onContactCreated); + connect(listener, &FriendListListener::contactDeleted, this, &ContactsListModel::onContactDeleted); + connect(listener, &FriendListListener::contactUpdated, this, &ContactsListModel::onContactUpdated); + connect(listener, &FriendListListener::syncStatusChanged, this, &ContactsListModel::onSyncStatusChanged); + connect(listener, &FriendListListener::presenceReceived, this, &ContactsListModel::onPresenceReceived); +} // ============================================================================= using namespace std; ContactsListModel::ContactsListModel (QObject *parent) : ProxyListModel(parent) { - mLinphoneFriends = CoreManager::getInstance()->getCore()->getFriendsLists().front(); - // Clean friends. - { - list> toRemove; - for (const auto &linphoneFriend : mLinphoneFriends->getFriends()) { - if (!linphoneFriend->getVcard()) - toRemove.push_back(linphoneFriend); - } - - for (const auto &linphoneFriend : toRemove) { - qWarning() << QStringLiteral("Remove one friend without vcard."); - mLinphoneFriends->removeFriend(linphoneFriend); - } - } - - // Init contacts with linphone friends list. - QQmlEngine *engine = App::getInstance()->getEngine(); - for (const auto &linphoneFriend : mLinphoneFriends->getFriends()) { - auto contact = QSharedPointer::create(linphoneFriend); - - // See: http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership - // The returned value must have a explicit parent or a QQmlEngine::CppOwnership. - engine->setObjectOwnership(contact.get(), QQmlEngine::CppOwnership); - - addContact(contact); - } + mFriendListListener = std::make_shared(); + connectTo(mFriendListListener.get()); + update(); } ContactsListModel::~ContactsListModel(){ @@ -65,7 +51,7 @@ ContactsListModel::~ContactsListModel(){ beginResetModel(); mOptimizedSearch.clear(); mList.clear(); - mLinphoneFriends = nullptr; + mLinphoneFriends.clear(); endResetModel(); } } @@ -75,7 +61,9 @@ bool ContactsListModel::removeRows (int row, int count, const QModelIndex &paren if (row < 0 || count < 0 || limit >= mList.count()) return false; - + + auto friendsList = CoreManager::getInstance()->getCore()->getFriendsLists(); + beginRemoveRows(parent, row, limit); for (int i = 0; i < count; ++i) { @@ -84,7 +72,8 @@ bool ContactsListModel::removeRows (int row, int count, const QModelIndex &paren mOptimizedSearch.remove(address.toString()); } - mLinphoneFriends->removeFriend(contact->mLinphoneFriend); + for(auto l : friendsList) + l->removeFriend(contact->mLinphoneFriend); emit contactRemoved(contact); } @@ -111,6 +100,10 @@ QSharedPointer ContactsListModel::findContactModelFromUsername (co } // ----------------------------------------------------------------------------- +ContactModel *ContactsListModel::getContactModelFromAddress (const QString& address) const{ + auto contact = findContactModelFromSipAddress(address); + return contact.get(); +} ContactModel *ContactsListModel::addContact (VcardModel *vcardModel) { // Try to merge vcardModel to an existing contact. @@ -123,7 +116,16 @@ ContactModel *ContactsListModel::addContact (VcardModel *vcardModel) { contact = QSharedPointer::create(vcardModel); App::getInstance()->getEngine()->setObjectOwnership(contact.get(), QQmlEngine::CppOwnership); - if (mLinphoneFriends->addFriend(contact->mLinphoneFriend) != linphone::FriendList::Status::OK) { + if( mLinphoneFriends.size() == 0){ + update();// Friends were not loaded correctly. Update them. + } + auto friendsList = CoreManager::getInstance()->getCore()->getDefaultFriendList(); + if( !friendsList){ + qWarning() << "There is no friends list available, cannot add a contact" ; + return nullptr; + } + + if (friendsList->addFriend(contact->mLinphoneFriend) != linphone::FriendList::Status::OK) { qWarning() << QStringLiteral("Unable to add contact from vcard:") << vcardModel; return nullptr; } @@ -131,9 +133,8 @@ ContactModel *ContactsListModel::addContact (VcardModel *vcardModel) { qInfo() << QStringLiteral("Add contact from vcard:") << contact.get() << vcardModel; // Make sure new subscribe is issued. - mLinphoneFriends->updateSubscriptions(); + friendsList->updateSubscriptions(); - addContact(contact); emit layoutChanged(); emit contactAdded(contact); @@ -177,3 +178,47 @@ void ContactsListModel::addContact (QSharedPointer contact) { mOptimizedSearch[address.toString()] = contact; } } + +void ContactsListModel::update(){ + beginResetModel(); + for(auto l : mLinphoneFriends) + l->removeListener(mFriendListListener); + mLinphoneFriends.clear(); + mOptimizedSearch.clear(); + mList.clear(); + endResetModel(); + + mLinphoneFriends = CoreManager::getInstance()->getCore()->getFriendsLists(); + + for(auto l : mLinphoneFriends){ + l->addListener(mFriendListListener); + for (const auto &linphoneFriend : l->getFriends()) { + onContactCreated(linphoneFriend); + } + } +} + +//------------------------------------------------------------------------------------------------ + +void ContactsListModel::onContactCreated(const std::shared_ptr & linphoneFriend){ + QQmlEngine *engine = App::getInstance()->getEngine(); + auto haveContact = std::find_if(mList.begin(), mList.end(), [linphoneFriend] (const QSharedPointer& item){ + return item.objectCast()->getFriend() == linphoneFriend; + }); + if(haveContact == mList.end()) { + auto contact = QSharedPointer::create(linphoneFriend); + // See: http://doc.qt.io/qt-5/qtqml-cppintegration-data.html#data-ownership + // The returned value must have a explicit parent or a QQmlEngine::CppOwnership. + engine->setObjectOwnership(contact.get(), QQmlEngine::CppOwnership); + addContact(contact); + } +} +void ContactsListModel::onContactDeleted(const std::shared_ptr & linphoneFriend){ +} +void ContactsListModel::onContactUpdated(const std::shared_ptr & newFriend, const std::shared_ptr & oldFriend){ +} +void ContactsListModel::onSyncStatusChanged(linphone::FriendList::SyncStatus status, const std::string & message){ +} +void ContactsListModel::onPresenceReceived(const std::list> & friends){ +} + diff --git a/linphone-app/src/components/contacts/ContactsListModel.hpp b/linphone-app/src/components/contacts/ContactsListModel.hpp index f6ea3b627..93768a758 100644 --- a/linphone-app/src/components/contacts/ContactsListModel.hpp +++ b/linphone-app/src/components/contacts/ContactsListModel.hpp @@ -33,6 +33,7 @@ namespace linphone { class ContactModel; class VcardModel; +class FriendListListener; class ContactsListModel : public ProxyListModel { friend class SipAddressesModel; @@ -48,10 +49,21 @@ public: QSharedPointer findContactModelFromSipAddress (const QString &sipAddress) const; QSharedPointer findContactModelFromUsername (const QString &username) const; + Q_INVOKABLE ContactModel *getContactModelFromAddress (const QString& address) const; Q_INVOKABLE ContactModel *addContact (VcardModel *vcardModel); Q_INVOKABLE void removeContact (ContactModel *contact); Q_INVOKABLE void cleanAvatars (); + Q_INVOKABLE void update (); + + void connectTo(FriendListListener * listener); + +public slots: + void onContactCreated(const std::shared_ptr & linphoneFriend); + void onContactDeleted(const std::shared_ptr & linphoneFriend); + void onContactUpdated(const std::shared_ptr & newFriend, const std::shared_ptr & oldFriend); + void onSyncStatusChanged(linphone::FriendList::SyncStatus status, const std::string & message); + void onPresenceReceived(const std::list> & friends); signals: void contactAdded (QSharedPointer); @@ -65,7 +77,8 @@ private: void addContact (QSharedPointer contact); QMap> mOptimizedSearch; - std::shared_ptr mLinphoneFriends; + std::list> mLinphoneFriends; + std::shared_ptr mFriendListListener; }; #endif // CONTACTS_LIST_MODEL_H_ diff --git a/linphone-app/src/components/core/CoreManager.cpp b/linphone-app/src/components/core/CoreManager.cpp index 01aca27c0..c4c013123 100644 --- a/linphone-app/src/components/core/CoreManager.cpp +++ b/linphone-app/src/components/core/CoreManager.cpp @@ -260,6 +260,7 @@ void CoreManager::createLinphoneCore (const QString &configPath) { Paths::getFactoryConfigFilePath(), nullptr ); + setDatabasesPaths(); // Enable LIME on your core to use encryption. mCore->enableLimeX3Dh(mCore->limeX3DhAvailable()); // Now see the CoreService.CreateGroupChatRoom to see how to create a secure chat room @@ -279,12 +280,13 @@ void CoreManager::createLinphoneCore (const QString &configPath) { QString userAgent = Utils::computeUserAgent(config); mCore->setUserAgent(Utils::appStringToCoreString(userAgent), mCore->getVersion()); mCore->start(); - setDatabasesPaths(); setOtherPaths(); mCore->enableFriendListSubscription(true); mCore->enableRecordAware(true); if(mCore->getAccountCreatorUrl() == "") mCore->setAccountCreatorUrl(Constants::DefaultFlexiAPIURL); + if( mCore->getAccountList().size() == 0) + mCore->setLogCollectionUploadServerUrl(Constants::DefaultUploadLogsServer); } void CoreManager::updateUserAgent(){ diff --git a/linphone-app/src/components/friend/FriendListListener.cpp b/linphone-app/src/components/friend/FriendListListener.cpp new file mode 100644 index 000000000..12d976792 --- /dev/null +++ b/linphone-app/src/components/friend/FriendListListener.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Belledonne Communications SARL. + * + * This file is part of linphone-desktop + * (see https://www.linphone.org). + * + * 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 . + */ + +#include "FriendListListener.hpp" +#include "../../utils/Utils.hpp" + +#include + +// ============================================================================= + +FriendListListener::FriendListListener(QObject *parent) : QObject(parent) { +} + +//-------------------------------------------------------------------- +void FriendListListener::onContactCreated(const std::shared_ptr & friendList, const std::shared_ptr & linphoneFriend) { + qDebug() << "onContactCreated: " << Utils::coreStringToAppString(linphoneFriend->getName()); + emit contactCreated(linphoneFriend); +} +void FriendListListener::onContactDeleted(const std::shared_ptr & friendList, const std::shared_ptr & linphoneFriend) { + qDebug() << "onContactDeleted: " << Utils::coreStringToAppString(linphoneFriend->getName()); + emit contactDeleted(linphoneFriend); +} +void FriendListListener::onContactUpdated(const std::shared_ptr & friendList, const std::shared_ptr & newFriend, const std::shared_ptr & oldFriend) { + qDebug() << "onContactUpdated: " << Utils::coreStringToAppString(newFriend->getName()); + emit contactUpdated(newFriend, oldFriend); +} +void FriendListListener::onSyncStatusChanged(const std::shared_ptr & friendList, linphone::FriendList::SyncStatus status, const std::string & message) { + qDebug() << "onSyncStatusChanged: [" << (int)status<<"] " << Utils::coreStringToAppString(message); + emit syncStatusChanged(status, message); +} +void FriendListListener::onPresenceReceived(const std::shared_ptr & friendList, const std::list> & friends) { + qDebug() << "onPresenceReceived: " <. + */ + +#ifndef FRIEND_LIST_LISTENER_H_ +#define FRIEND_LIST_LISTENER_H_ + + +#include + +#include + +// ============================================================================= + +class FriendListListener : public QObject, public linphone::FriendListListener { + Q_OBJECT + +public: + FriendListListener (QObject *parent = nullptr); + virtual void onContactCreated(const std::shared_ptr & friendList, const std::shared_ptr & linphoneFriend) override; + virtual void onContactDeleted(const std::shared_ptr & friendList, const std::shared_ptr & linphoneFriend) override; + virtual void onContactUpdated(const std::shared_ptr & friendList, const std::shared_ptr & newFriend, const std::shared_ptr & oldFriend) override; + virtual void onSyncStatusChanged(const std::shared_ptr & friendList, linphone::FriendList::SyncStatus status, const std::string & message) override; + virtual void onPresenceReceived(const std::shared_ptr & friendList, const std::list> & friends) override; + +signals: + void contactCreated(const std::shared_ptr & linphoneFriend); + void contactDeleted(const std::shared_ptr & linphoneFriend); + void contactUpdated(const std::shared_ptr & newFriend, const std::shared_ptr & oldFriend); + void syncStatusChanged(linphone::FriendList::SyncStatus status, const std::string & message); + void presenceReceived(const std::list> & friends); +}; + +#endif diff --git a/linphone-app/src/components/other/timeZone/TimeZoneProxyModel.cpp b/linphone-app/src/components/other/timeZone/TimeZoneProxyModel.cpp index 0d837c0a9..514a7f031 100644 --- a/linphone-app/src/components/other/timeZone/TimeZoneProxyModel.cpp +++ b/linphone-app/src/components/other/timeZone/TimeZoneProxyModel.cpp @@ -27,6 +27,7 @@ // ----------------------------------------------------------------------------- TimeZoneProxyModel::TimeZoneProxyModel (QObject *parent) : SortFilterProxyModel(parent) { + mDeleteSourceModel = true; setSourceModel(new TimeZoneListModel(parent)); sort(0); } diff --git a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp index 05f132cd9..ea2e9b6c8 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp +++ b/linphone-app/src/components/participant/ParticipantDeviceListModel.cpp @@ -58,6 +58,7 @@ void ParticipantDeviceListModel::initConferenceModel(){ updateDevices(conferenceModel->getConference()->getParticipantDeviceList(), false); qDebug() << "Conference have " << mList.size() << " devices"; + connect(conferenceModel.get(), &ConferenceModel::activeSpeakerParticipantDevice, this, &ParticipantDeviceListModel::onActiveSpeakerParticipantDevice); connect(conferenceModel.get(), &ConferenceModel::participantAdded, this, &ParticipantDeviceListModel::onParticipantAdded); connect(conferenceModel.get(), &ConferenceModel::participantRemoved, this, &ParticipantDeviceListModel::onParticipantRemoved); connect(conferenceModel.get(), &ConferenceModel::participantDeviceAdded, this, &ParticipantDeviceListModel::onParticipantDeviceAdded); @@ -98,6 +99,8 @@ void ParticipantDeviceListModel::updateDevices(const std::list deviceToAdd){ + auto deviceToAddAddr = deviceToAdd->getAddress(); + int row = 0; qDebug() << "Adding device " << deviceToAdd->getAddress()->asString().c_str(); for(auto item : mList) { auto deviceModel = item.objectCast(); @@ -105,7 +108,12 @@ bool ParticipantDeviceListModel::add(std::shared_ptrupdateVideoEnabled(); return false; + }else if(deviceToAddAddr->equal(deviceModel->getDevice()->getAddress())){// Address is the same (same device) but the model is using another linphone object. Replace it. + deviceModel->updateVideoEnabled(); + removeRow(row); + break; } + ++row; } bool addMe = isMe(deviceToAdd); auto deviceModel = ParticipantDeviceModel::create(mCallModel, deviceToAdd, addMe); @@ -113,9 +121,18 @@ bool ParticipantDeviceListModel::add(std::shared_ptr(deviceModel); qDebug() << "Device added. Count=" << mList.count(); + QStringList debugDevices; + for(auto i : mList){ + auto item = i.objectCast(); + debugDevices.push_back( item->getAddress()); + } + qDebug() << debugDevices.join("\n"); if( addMe){ qDebug() << "Added a me device"; emit meChanged(); + }else if(mList.size() == 1 || (mList.size() == 2 && isMe(mList.front().objectCast()->getDevice()))){ + mActiveSpeaker = mList.back().objectCast(); + emit activeSpeakerChanged(); } return true; } @@ -126,7 +143,6 @@ bool ParticipantDeviceListModel::remove(std::shared_ptr(); if( device->getDevice() == deviceToRemove){ device->updateVideoEnabled(); - mActiveSpeakers.removeAll(device.get()); removeRow(row); return true; }else @@ -163,14 +179,8 @@ QSharedPointer ParticipantDeviceListModel::getMe(int * i return nullptr; } -ParticipantDeviceModel* ParticipantDeviceListModel::getLastActiveSpeaking() const{ - if( mActiveSpeakers.size() == 0){ - if( mList.size() == 0) - return getMe().get(); - else - return mList.back().objectCast().get(); - }else - return mActiveSpeakers.first(); +ParticipantDeviceModel* ParticipantDeviceListModel::getActiveSpeakerModel() const{ + return mActiveSpeaker.get(); } bool ParticipantDeviceListModel::isMe(std::shared_ptr deviceToCheck)const{ @@ -280,6 +290,13 @@ void ParticipantDeviceListModel::onParticipantDeviceMediaAvailabilityChanged(con else onParticipantDeviceAdded(participantDevice); } +void ParticipantDeviceListModel::onActiveSpeakerParticipantDevice(const std::shared_ptr& participantDevice){ + auto device = get(participantDevice); + if( device){ + mActiveSpeaker = device; + emit activeSpeakerChanged(); + } +} void ParticipantDeviceListModel::onParticipantDeviceIsSpeakingChanged(const std::shared_ptr & participantDevice, bool isSpeaking){ auto device = get(participantDevice); @@ -288,14 +305,5 @@ void ParticipantDeviceListModel::onParticipantDeviceIsSpeakingChanged(const std: } void ParticipantDeviceListModel::onParticipantDeviceSpeaking(){ - auto deviceModel = qobject_cast(sender()); - bool changed = false; - // Me should not be in the list. - if( !deviceModel->isMe() && (mActiveSpeakers.size() == 0 || deviceModel->getIsSpeaking())) {// Ensure to have at least one last active speaker - changed = mActiveSpeakers.removeAll(deviceModel) > 0; - mActiveSpeakers.push_front(deviceModel); - changed = true; - } - if(changed) - emit participantSpeaking(deviceModel); + } diff --git a/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp b/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp index 318e46730..ed5edc3c1 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp +++ b/linphone-app/src/components/participant/ParticipantDeviceListModel.hpp @@ -47,12 +47,13 @@ public: bool remove(std::shared_ptr deviceToAdd); QSharedPointer get(std::shared_ptr deviceToGet, int * index = nullptr); QSharedPointer getMe(int * index = nullptr)const; - ParticipantDeviceModel* getLastActiveSpeaking() const; + ParticipantDeviceModel* getActiveSpeakerModel() const; bool isMe(std::shared_ptr device)const; bool isMeAlone() const; public slots: + void onActiveSpeakerParticipantDevice(const std::shared_ptr& participantDevice); void onConferenceModelChanged (); void onSecurityLevelChanged(std::shared_ptr device); void onParticipantAdded(const std::shared_ptr & participant); @@ -66,6 +67,7 @@ public slots: void onParticipantDeviceSpeaking(); signals: + void activeSpeakerChanged(); void securityLevelChanged(std::shared_ptr device); void participantSpeaking(ParticipantDeviceModel *speakingDevice); void conferenceCreated(); @@ -73,7 +75,8 @@ signals: private: CallModel * mCallModel = nullptr; - QList mActiveSpeakers;// First item is last speaker + QSharedPointer mActiveSpeaker; + //QList mActiveSpeakers;// First item is last speaker bool mInitialized = false; }; diff --git a/linphone-app/src/components/participant/ParticipantDeviceProxyModel.cpp b/linphone-app/src/components/participant/ParticipantDeviceProxyModel.cpp index b05c9129d..b29b4c6bd 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceProxyModel.cpp +++ b/linphone-app/src/components/participant/ParticipantDeviceProxyModel.cpp @@ -31,6 +31,10 @@ // ============================================================================= ParticipantDeviceProxyModel::ParticipantDeviceProxyModel (QObject *parent) : SortFilterProxyModel(parent){ + mDeleteSourceModel = true; +} + +ParticipantDeviceProxyModel::~ParticipantDeviceProxyModel(){ } bool ParticipantDeviceProxyModel::filterAcceptsRow ( @@ -57,9 +61,9 @@ ParticipantDeviceModel *ParticipantDeviceProxyModel::getAt(int row){ return sourceModel()->data(sourceIndex).value(); } -ParticipantDeviceModel* ParticipantDeviceProxyModel::getLastActiveSpeaking(){ +ParticipantDeviceModel* ParticipantDeviceProxyModel::getActiveSpeakerModel(){ auto listModel = qobject_cast(sourceModel()); - return listModel ? listModel->getLastActiveSpeaking() : nullptr; + return listModel ? listModel->getActiveSpeakerModel() : nullptr; } CallModel * ParticipantDeviceProxyModel::getCallModel() const{ @@ -74,28 +78,31 @@ ParticipantDeviceModel * ParticipantDeviceProxyModel::getMe() const{ bool ParticipantDeviceProxyModel::isShowMe() const{ return mShowMe; } - + +void ParticipantDeviceProxyModel::connectTo(ParticipantDeviceListModel* model){ + connect(model, &ParticipantDeviceListModel::countChanged, this, &ParticipantDeviceProxyModel::onCountChanged); + connect(model, &ParticipantDeviceListModel::participantSpeaking, this, &ParticipantDeviceProxyModel::onParticipantSpeaking); + connect(model, &ParticipantDeviceListModel::conferenceCreated, this, &ParticipantDeviceProxyModel::conferenceCreated); + connect(model, &ParticipantDeviceListModel::meChanged, this, &ParticipantDeviceProxyModel::meChanged); + connect(model, &ParticipantDeviceListModel::activeSpeakerChanged, this, &ParticipantDeviceProxyModel::activeSpeakerChanged); +} void ParticipantDeviceProxyModel::setCallModel(CallModel * callModel){ setFilterType(1); mCallModel = callModel; - auto sourceModel = new ParticipantDeviceListModel(mCallModel); - connect(sourceModel, &ParticipantDeviceListModel::countChanged, this, &ParticipantDeviceProxyModel::onCountChanged); - connect(sourceModel, &ParticipantDeviceListModel::participantSpeaking, this, &ParticipantDeviceProxyModel::onParticipantSpeaking); - connect(sourceModel, &ParticipantDeviceListModel::conferenceCreated, this, &ParticipantDeviceProxyModel::conferenceCreated); - connect(sourceModel, &ParticipantDeviceListModel::meChanged, this, &ParticipantDeviceProxyModel::meChanged); - setSourceModel(sourceModel); + deleteSourceModel(); + auto newSourceModel = new ParticipantDeviceListModel(mCallModel); + connectTo(newSourceModel); + setSourceModel(newSourceModel); emit countChanged(); emit meChanged(); } void ParticipantDeviceProxyModel::setParticipant(ParticipantModel * participant){ setFilterType(0); - auto sourceModel = participant->getParticipantDevices().get(); - connect(sourceModel, &ParticipantDeviceListModel::countChanged, this, &ParticipantDeviceProxyModel::countChanged); - connect(sourceModel, &ParticipantDeviceListModel::participantSpeaking, this, &ParticipantDeviceProxyModel::onParticipantSpeaking); - connect(sourceModel, &ParticipantDeviceListModel::conferenceCreated, this, &ParticipantDeviceProxyModel::conferenceCreated); - connect(sourceModel, &ParticipantDeviceListModel::meChanged, this, &ParticipantDeviceProxyModel::meChanged); - setSourceModel(sourceModel); + deleteSourceModel(); + auto newSourceModel = participant->getParticipantDevices().get(); + connectTo(newSourceModel); + setSourceModel(newSourceModel); emit countChanged(); emit meChanged(); } diff --git a/linphone-app/src/components/participant/ParticipantDeviceProxyModel.hpp b/linphone-app/src/components/participant/ParticipantDeviceProxyModel.hpp index ecdedfd02..dfa0b885f 100644 --- a/linphone-app/src/components/participant/ParticipantDeviceProxyModel.hpp +++ b/linphone-app/src/components/participant/ParticipantDeviceProxyModel.hpp @@ -42,12 +42,15 @@ public: Q_PROPERTY(CallModel * callModel READ getCallModel WRITE setCallModel NOTIFY callModelChanged) Q_PROPERTY(bool showMe READ isShowMe WRITE setShowMe NOTIFY showMeChanged) Q_PROPERTY(ParticipantDeviceModel * me READ getMe NOTIFY meChanged) + Q_PROPERTY(ParticipantDeviceModel* activeSpeaker READ getActiveSpeakerModel NOTIFY activeSpeakerChanged) ParticipantDeviceProxyModel (QObject *parent = nullptr); + ~ParticipantDeviceProxyModel(); Q_INVOKABLE ParticipantDeviceModel* getAt(int row); - Q_INVOKABLE ParticipantDeviceModel* getLastActiveSpeaking(); - ParticipantDeviceModel * getMe() const; + ParticipantDeviceModel* getActiveSpeakerModel(); + ParticipantDeviceModel* getMe() const; + CallModel * getCallModel() const; bool isShowMe() const; @@ -56,11 +59,14 @@ public: void setParticipant(ParticipantModel * participant); void setShowMe(const bool& show); + void connectTo(ParticipantDeviceListModel* model); + public slots: void onCountChanged(); void onParticipantSpeaking(ParticipantDeviceModel * speakingDevice); signals: + void activeSpeakerChanged(); void callModelChanged(); void showMeChanged(); void meChanged(); @@ -71,7 +77,6 @@ protected: virtual bool filterAcceptsRow (int sourceRow, const QModelIndex &sourceParent) const override; virtual bool lessThan (const QModelIndex &left, const QModelIndex &right) const override; - QSharedPointer mDevices; CallModel * mCallModel; bool mShowMe = true; }; diff --git a/linphone-app/src/components/participant/ParticipantProxyModel.cpp b/linphone-app/src/components/participant/ParticipantProxyModel.cpp index 6abab1acd..5e32d4729 100644 --- a/linphone-app/src/components/participant/ParticipantProxyModel.cpp +++ b/linphone-app/src/components/participant/ParticipantProxyModel.cpp @@ -92,8 +92,11 @@ void ParticipantProxyModel::setChatRoomModel(ChatRoomModel * chatRoomModel){ connect(participants, &ParticipantListModel::countChanged, this, &ParticipantProxyModel::countChanged); setSourceModel(participants); emit participantListModelChanged(); - for(int i = 0 ; i < participants->getCount() ; ++i) - emit addressAdded(participants->getAt(i)->getSipAddress()); + for(int i = 0 ; i < participants->getCount() ; ++i) { + auto participant = participants->getAt(i); + connect(participant.get(), &ParticipantModel::invitationTimeout, this, &ParticipantProxyModel::removeModel); + emit addressAdded(participant->getSipAddress()); + } }else if(!sourceModel()){ auto model = new ParticipantListModel((ChatRoomModel*)nullptr, this); connect(model, &ParticipantListModel::countChanged, this, &ParticipantProxyModel::countChanged); @@ -113,8 +116,11 @@ void ParticipantProxyModel::setConferenceModel(ConferenceModel * conferenceModel connect(participants, &ParticipantListModel::countChanged, this, &ParticipantProxyModel::countChanged); setSourceModel(participants); emit participantListModelChanged(); - for(int i = 0 ; i < participants->getCount() ; ++i) - emit addressAdded(participants->getAt(i)->getSipAddress()); + for(int i = 0 ; i < participants->getCount() ; ++i) { + auto participant = participants->getAt(i); + connect(participant.get(), &ParticipantModel::invitationTimeout, this, &ParticipantProxyModel::removeModel); + emit addressAdded(participant->getSipAddress()); + } }else if(!sourceModel()){ auto model = new ParticipantListModel((ConferenceModel*)nullptr, this); connect(model, &ParticipantListModel::countChanged, this, &ParticipantProxyModel::countChanged); @@ -144,12 +150,12 @@ void ParticipantProxyModel::addAddress(const QString& address){ ParticipantListModel * participantsModel = qobject_cast(sourceModel()); if(!participantsModel->contains(address)){ QSharedPointer participant = QSharedPointer::create(nullptr); + connect(participant.get(), &ParticipantModel::invitationTimeout, this, &ParticipantProxyModel::removeModel); participant->setSipAddress(address); participantsModel->add(participant); if(mChatRoomModel && mChatRoomModel->getChatRoom()){// Invite and wait for its creation - mChatRoomModel->getChatRoom()->addParticipant(Utils::interpretUrl(address)); - connect(participant.get(), &ParticipantModel::invitationTimeout, this, &ParticipantProxyModel::removeModel); participant->startInvitation(); + mChatRoomModel->getChatRoom()->addParticipant(Utils::interpretUrl(address)); } if( mConferenceModel && mConferenceModel->getConference()){ auto addressToInvite = Utils::interpretUrl(address); @@ -158,23 +164,18 @@ void ParticipantProxyModel::addAddress(const QString& address){ auto haveCall = std::find_if(currentCalls.begin(), currentCalls.end(), [addressToInvite](const std::shared_ptr& call){ return call->getRemoteAddress()->weakEqual(addressToInvite); }); + participant->startInvitation(); if( haveCall == currentCalls.end()) mConferenceModel->getConference()->addParticipant(addressToInvite); else{ runningCallsToAdd.push_back(*haveCall); mConferenceModel->getConference()->addParticipants(runningCallsToAdd); } - - /* std::list> addressesToInvite; addressesToInvite.push_back(addressToInvite); auto callParameters = CoreManager::getInstance()->getCore()->createCallParams(mConferenceModel->getConference()->getCall()); - mConferenceModel->getConference()->inviteParticipants(addressesToInvite, callParameters);*/ - - connect(participant.get(), &ParticipantModel::invitationTimeout, this, &ParticipantProxyModel::removeModel); - participant->startInvitation(); - + mConferenceModel->getConference()->inviteParticipants(addressesToInvite, callParameters);*/ } emit countChanged(); emit addressAdded(address); diff --git a/linphone-app/src/components/settings/AccountSettingsModel.cpp b/linphone-app/src/components/settings/AccountSettingsModel.cpp index e0b73b2a0..44d1a447e 100644 --- a/linphone-app/src/components/settings/AccountSettingsModel.cpp +++ b/linphone-app/src/components/settings/AccountSettingsModel.cpp @@ -507,7 +507,11 @@ QString AccountSettingsModel::getPrimarySipAddress () const { } QString AccountSettingsModel::getDefaultAccountDomain() const{ - return Utils::coreStringToAppString(CoreManager::getInstance()->getCore()->getDefaultAccount()->getParams()->getDomain()); + auto account = CoreManager::getInstance()->getCore()->getDefaultAccount(); + if(account) + return Utils::coreStringToAppString(account->getParams()->getDomain()); + else + return ""; } // ----------------------------------------------------------------------------- diff --git a/linphone-app/src/components/settings/SettingsModel.cpp b/linphone-app/src/components/settings/SettingsModel.cpp index ce815e893..c599ebad6 100644 --- a/linphone-app/src/components/settings/SettingsModel.cpp +++ b/linphone-app/src/components/settings/SettingsModel.cpp @@ -69,7 +69,6 @@ SettingsModel::SettingsModel (QObject *parent) : QObject(parent) { connect(coreManager->getAccountSettingsModel(), &AccountSettingsModel::accountSettingsUpdated, this, &SettingsModel::videoConferenceEnabledChanged); connect(coreManager->getAccountSettingsModel(), &AccountSettingsModel::accountSettingsUpdated, this, &SettingsModel::secureChatEnabledChanged); - configureRlsUri(); } @@ -548,12 +547,9 @@ static inline QVariantMap createMapFromVideoDefinition (const shared_ptrgetCore()->videoSupported()); - map["name"] = QStringLiteral("Bad EGG"); map["width"] = QStringLiteral("?????"); map["height"] = QStringLiteral("?????"); - return map; } @@ -987,9 +983,6 @@ void SettingsModel::setMediaEncryption (MediaEncryption encryption) { if (encryption == getMediaEncryption()) return; - if (encryption != SettingsModel::MediaEncryptionZrtp) - setLimeState(false); - CoreManager::getInstance()->getCore()->setMediaEncryption( static_cast(encryption) ); @@ -1032,10 +1025,7 @@ void SettingsModel::setLimeState (const bool& state) { if (state == getLimeState()) return; - if (state) - setMediaEncryption(SettingsModel::MediaEncryptionZrtp); - - CoreManager::getInstance()->getCore()->enableLimeX3Dh(!state); + CoreManager::getInstance()->getCore()->enableLimeX3Dh(state); emit limeStateChanged(state); } diff --git a/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp b/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp index a76cf3d1b..b4b17cc7e 100644 --- a/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp +++ b/linphone-app/src/components/sip-addresses/SipAddressesModel.cpp @@ -455,8 +455,10 @@ void SipAddressesModel::handleMessageCountReset (ChatRoomModel *chatRoomModel) { } void SipAddressesModel::handleMessageSent (const shared_ptr &message) { - const QString peerAddress(Utils::coreStringToAppString(message->getChatRoom()->getPeerAddress()->asStringUriOnly())); - addOrUpdateSipAddress(peerAddress, message); + if(message->getChatRoom() && message->getChatRoom()->getPeerAddress()){ + const QString peerAddress(Utils::coreStringToAppString(message->getChatRoom()->getPeerAddress()->asStringUriOnly())); + addOrUpdateSipAddress(peerAddress, message); + } } void SipAddressesModel::handleIsComposingChanged (const shared_ptr &chatRoom) { diff --git a/linphone-app/src/components/timeline/TimelineListModel.cpp b/linphone-app/src/components/timeline/TimelineListModel.cpp index c9a4f521d..c181d7452 100644 --- a/linphone-app/src/components/timeline/TimelineListModel.cpp +++ b/linphone-app/src/components/timeline/TimelineListModel.cpp @@ -71,7 +71,6 @@ TimelineListModel::TimelineListModel(const TimelineListModel* model){ auto newItem = qobject_cast(item)->clone(); connect(newItem.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool))); connect(newItem.get(), &TimelineModel::chatRoomDeleted, this, &TimelineListModel::onChatRoomDeleted); - connect(newItem->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); mList << newItem; } } @@ -141,7 +140,6 @@ QSharedPointer TimelineListModel::getTimeline(std::shared_ptr model = TimelineModel::create(this, chatRoom); if(model){ connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool))); - connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); add(model); return model; } @@ -189,7 +187,6 @@ QSharedPointer TimelineListModel::getChatRoomModel(std::shared_pt QSharedPointer model = TimelineModel::create(this, chatRoom); if(model){ connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool))); - connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); add(model); return model->mChatRoomModel; } @@ -249,7 +246,17 @@ void TimelineListModel::updateTimelines () { allChatRooms.remove_if([](std::shared_ptr chatRoom){ if( ChatRoomModel::isTerminated(chatRoom) && chatRoom->getUnreadMessagesCount() > 0) chatRoom->markAsRead(); - return !chatRoom->hasCapability((int)linphone::ChatRoomCapabilities::Basic) && chatRoom->getConferenceAddress() && chatRoom->getHistoryEventsSize() == 0; + if(chatRoom->getState() == linphone::ChatRoom::State::Deleted) + return true; + if(!chatRoom->hasCapability((int)linphone::ChatRoomCapabilities::Basic)){ + auto conferenceAddress = chatRoom->getConferenceAddress(); + if( conferenceAddress && conferenceAddress->getDomain() == Constants::LinphoneDomain) { + QString conferenceAddressStr = Utils::coreStringToAppString(conferenceAddress->asStringUriOnly()); + if( conferenceAddressStr.contains("conf-id")) + return true; + } + } + return false; }); //Remove no more chat rooms @@ -294,7 +301,6 @@ void TimelineListModel::updateTimelines () { QSharedPointer model = TimelineModel::create(this, dbChatRoom, callLogs); if( model){ connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool))); - connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); add(model); } } @@ -306,12 +312,10 @@ void TimelineListModel::add (QSharedPointer timeline){ auto chatRoomModel = timeline->getChatRoomModel(); auto chatRoom = chatRoomModel->getChatRoom(); connect(timeline.get(), &TimelineModel::chatRoomDeleted, this, &TimelineListModel::onChatRoomDeleted); - if( !chatRoomModel->haveConferenceAddress() || chatRoom->getHistoryEventsSize() != 0) { - connect(chatRoomModel, &ChatRoomModel::lastUpdateTimeChanged, this, &TimelineListModel::updated); - ProxyListModel::add(timeline); - emit layoutChanged(); - emit countChanged(); - } + connect(chatRoomModel, &ChatRoomModel::lastUpdateTimeChanged, this, &TimelineListModel::updated); + ProxyListModel::add(timeline); + emit layoutChanged(); + emit countChanged(); } void TimelineListModel::removeChatRoomModel(QSharedPointer model){ @@ -334,7 +338,10 @@ void TimelineListModel::select(ChatRoomModel * chatRoomModel){ if(chatRoomModel) { auto timeline = getTimeline(chatRoomModel->getChatRoom(), false); if(timeline){ - timeline->setSelected(true); + if(timeline->isUpdating()) + timeline->delaySelected(); + else + timeline->setSelected(true); } } } @@ -356,7 +363,6 @@ void TimelineListModel::onChatRoomStateChanged(const std::shared_ptr model = TimelineModel::create(this, chatRoom); if(model){ connect(model.get(), SIGNAL(selectedChanged(bool)), this, SLOT(onSelectedHasChanged(bool))); - connect(model->getChatRoomModel(), &ChatRoomModel::allEntriesRemoved, this, &TimelineListModel::removeChatRoomModel); add(model); } }else if(state == linphone::ChatRoom::State::Deleted || state == linphone::ChatRoom::State::Terminated){ @@ -368,6 +374,11 @@ void TimelineListModel::onChatRoomStateChanged(const std::shared_ptr chatRoom, cons QObject::connect(this, &TimelineModel::selectedChanged, this, &TimelineModel::updateUnreadCount); QObject::connect(CoreManager::getInstance()->getAccountSettingsModel(), &AccountSettingsModel::defaultAccountChanged, this, &TimelineModel::onDefaultAccountChanged); QObject::connect(mChatRoomModel.get(), &ChatRoomModel::chatRoomDeleted, this, &TimelineModel::onChatRoomDeleted); + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::updatingChanged, this, &TimelineModel::updatingChanged); + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::stateChanged, this, &TimelineModel::onChatRoomStateChanged); } if(chatRoom){ mChatRoomListener = std::make_shared(); @@ -103,6 +105,8 @@ TimelineModel::TimelineModel(const TimelineModel * model){ QObject::connect(this, &TimelineModel::selectedChanged, this, &TimelineModel::updateUnreadCount); QObject::connect(CoreManager::getInstance()->getAccountSettingsModel(), &AccountSettingsModel::defaultAccountChanged, this, &TimelineModel::onDefaultAccountChanged); QObject::connect(mChatRoomModel.get(), &ChatRoomModel::chatRoomDeleted, this, &TimelineModel::onChatRoomDeleted); + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::updatingChanged, this, &TimelineModel::updatingChanged); + QObject::connect(mChatRoomModel.get(), &ChatRoomModel::stateChanged, this, &TimelineModel::onChatRoomStateChanged); } if(mChatRoomModel->getChatRoom()){ mChatRoomListener = model->mChatRoomListener; @@ -141,6 +145,10 @@ int TimelineModel::getPresenceStatus() const{ return 0; } +bool TimelineModel::isUpdating() const{ + return !mChatRoomModel || mChatRoomModel->isUpdating(); +} + ChatRoomModel *TimelineModel::getChatRoomModel() const{ return mChatRoomModel.get(); } @@ -165,6 +173,15 @@ void TimelineModel::setSelected(const bool& selected){ } } +void TimelineModel::delaySelected(){ + if( mChatRoomModel->getState() == LinphoneEnums::ChatRoomStateCreated){ + QTimer::singleShot(200, [&](){// Delay process in order to let GUI time for Timeline building/linking before doing actions + setSelected(true); + }); + }else + mDelaySelection = true; +} + void TimelineModel::updateUnreadCount(){ if(!mSelected){// updateUnreadCount is called when selected has changed;: So if mSelected is false then we are going out of it. mChatRoomModel->resetMessageCount();// The reset will appear when the chat room has "mark as read enabled", that means that we should have read messages when going out. @@ -230,4 +247,11 @@ void TimelineModel::onChatMessageParticipantImdnStateChanged(const std::shared_p void TimelineModel::onChatRoomDeleted(){ emit chatRoomDeleted(); +} + +void TimelineModel::onChatRoomStateChanged(){ + if(mDelaySelection && mChatRoomModel->getState() == LinphoneEnums::ChatRoomStateCreated){ + mDelaySelection = false; + setSelected(true); + } } \ No newline at end of file diff --git a/linphone-app/src/components/timeline/TimelineModel.hpp b/linphone-app/src/components/timeline/TimelineModel.hpp index 1c33560e1..34cc2a044 100644 --- a/linphone-app/src/components/timeline/TimelineModel.hpp +++ b/linphone-app/src/components/timeline/TimelineModel.hpp @@ -52,6 +52,7 @@ public: Q_PROPERTY(ChatRoomModel* chatRoomModel READ getChatRoomModel CONSTANT) Q_PROPERTY(bool selected MEMBER mSelected WRITE setSelected NOTIFY selectedChanged) + Q_PROPERTY(bool updating READ isUpdating NOTIFY updatingChanged) QString getFullPeerAddress() const; @@ -61,7 +62,10 @@ public: QString getAvatar() const; int getPresenceStatus() const; + bool isUpdating() const; + void setSelected(const bool& selected); + void delaySelected(); Q_INVOKABLE ChatRoomModel* getChatRoomModel() const; @@ -102,6 +106,7 @@ public slots: void updateUnreadCount(); void onDefaultAccountChanged(); void onChatRoomDeleted(); + void onChatRoomStateChanged(); signals: void fullPeerAddressChanged(); @@ -112,9 +117,12 @@ signals: void selectedChanged(bool selected); void conferenceLeft(); void chatRoomDeleted(); + void updatingChanged(); private: + bool mDelaySelection = false; + void connectTo(ChatRoomListener * listener); std::shared_ptr mChatRoomListener; diff --git a/linphone-app/src/utils/Constants.cpp b/linphone-app/src/utils/Constants.cpp index 8e4483ab2..1d6191af5 100644 --- a/linphone-app/src/utils/Constants.cpp +++ b/linphone-app/src/utils/Constants.cpp @@ -65,6 +65,7 @@ constexpr int Constants::ThumbnailImageFileHeight; constexpr qint64 Constants::FileSizeLimit; constexpr char Constants::DefaultXmlrpcUri[]; +constexpr char Constants::DefaultUploadLogsServer[]; constexpr char Constants::DefaultConferenceURI[]; constexpr char Constants::DefaultVideoConferenceURI[]; constexpr char Constants::DefaultLimeServerURL[]; diff --git a/linphone-app/src/utils/Constants.hpp b/linphone-app/src/utils/Constants.hpp index 4d490992b..2140a8a52 100644 --- a/linphone-app/src/utils/Constants.hpp +++ b/linphone-app/src/utils/Constants.hpp @@ -36,21 +36,17 @@ public: //---------------------------------------------------------------------------------- - static constexpr char WindowIconPath[] = ":/assets/images/linphone_logo.svg"; static constexpr char DefaultLocale[] = "en"; - - static constexpr char ApplicationMinimalQtVersion[] = "5.10.0"; static constexpr char DefaultFont[] = "Noto Sans"; static constexpr size_t MaxLogsCollectionSize = 10485760*5; // 50MB. - #ifdef ENABLE_UPDATE_CHECK static constexpr int VersionUpdateCheckInterval = 86400000; // 24 hours in milliseconds. #endif // ifdef ENABLE_UPDATE_CHECK static constexpr char DefaultXmlrpcUri[] = "https://subscribe.linphone.org:444/wizard.php"; - static constexpr char LinphoneDomain[] = "sip.linphone.org"; + static constexpr char DefaultUploadLogsServer[] = "https://www.linphone.org:444/lft.php"; static constexpr char DefaultContactParameters[] = "message-expires=604800"; static constexpr char DefaultContactParametersOnRemove[] = "message-expires=0"; static constexpr int DefaultExpires = 3600; @@ -69,9 +65,6 @@ public: static constexpr char LinphoneBZip2_dll[] = "https://www.linphone.org/releases/windows/tools/bzip2/bzip2.dll"; static constexpr char DefaultRlsUri[] = "sips:rls@sip.linphone.org"; static constexpr char DefaultLogsEmail[] = "linphone-desktop@belledonne-communications.com"; - static constexpr char DefaultConferenceURI[] = "sip:conference-factory@sip.linphone.org"; - static constexpr char DefaultVideoConferenceURI[] = "sip:videoconference-factory@sip.linphone.org"; - static constexpr char DefaultLimeServerURL[] = "https://lime.linphone.org/lime-server/lime-server.php"; static constexpr char DefaultFlexiAPIURL[] = "http://fs-test-sandbox.linphone.org/flexiapi/api/";// Need "/" at the end static constexpr char RemoteProvisioningURL[] = "http://fs-test-sandbox.linphone.org/flexiapi/provisioning"; @@ -94,7 +87,18 @@ public: static constexpr qint64 FileSizeLimit = 524288000;// In Bytes. static constexpr int ThumbnailImageFileWidth = 100; static constexpr int ThumbnailImageFileHeight = 100; + +//-------------------------------------------------------------------------------- +// LINPHONE +//-------------------------------------------------------------------------------- + static constexpr char LinphoneDomain[] = "sip.linphone.org"; // Use for checking if config are a Linphone + static constexpr char WindowIconPath[] = ":/assets/images/linphone_logo.svg"; + static constexpr char ApplicationMinimalQtVersion[] = "5.10.0"; + static constexpr char DefaultConferenceURI[] = "sip:conference-factory@sip.linphone.org"; // Default for a Linphone account + static constexpr char DefaultVideoConferenceURI[] = "sip:videoconference-factory@sip.linphone.org"; // Default for a Linphone account + static constexpr char DefaultLimeServerURL[] = "https://lime.linphone.org/lime-server/lime-server.php"; // Default for a Linphone account + static constexpr char PathAssistantConfig[] = "/" EXECUTABLE_NAME "/assistant/"; static constexpr char PathAvatars[] = "/avatars/"; static constexpr char PathCaptures[] = "/" EXECUTABLE_NAME "/captures/"; @@ -146,8 +150,6 @@ public: // 3 = CPIM on basic chat rooms // 4 = RTP bundle mode // 5 = Video Conference URI - - //-------------------------------------------------------------------------------- // CISCO //-------------------------------------------------------------------------------- diff --git a/linphone-app/src/utils/LinphoneEnums.cpp b/linphone-app/src/utils/LinphoneEnums.cpp index 90026d573..5b8aa6a7a 100644 --- a/linphone-app/src/utils/LinphoneEnums.cpp +++ b/linphone-app/src/utils/LinphoneEnums.cpp @@ -25,14 +25,19 @@ // ============================================================================= void LinphoneEnums::registerMetaTypes(){ - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); } linphone::MediaEncryption LinphoneEnums::toLinphone(const LinphoneEnums::MediaEncryption& data){ @@ -63,6 +68,14 @@ LinphoneEnums::ChatMessageState LinphoneEnums::fromLinphone(const linphone::Chat return static_cast(data); } +linphone::ChatRoom::State LinphoneEnums::toLinphone(const LinphoneEnums::ChatRoomState& data){ + return static_cast(data); +} + +LinphoneEnums::ChatRoomState LinphoneEnums::fromLinphone(const linphone::ChatRoom::State& data){ + return static_cast(data); +} + linphone::Call::Status LinphoneEnums::toLinphone(const LinphoneEnums::CallStatus& data){ return static_cast(data); } @@ -89,6 +102,14 @@ LinphoneEnums::ConferenceInfoState LinphoneEnums::fromLinphone(const linphone::C return static_cast(state); } +linphone::ConferenceScheduler::State LinphoneEnums::toLinphone(const LinphoneEnums::ConferenceSchedulerState& state){ + return static_cast(state); +} + +LinphoneEnums::ConferenceSchedulerState LinphoneEnums::fromLinphone(const linphone::ConferenceScheduler::State& state){ + return static_cast(state); +} + linphone::ParticipantDeviceState LinphoneEnums::toLinphone(const LinphoneEnums::ParticipantDeviceState& state){ return static_cast(state); } diff --git a/linphone-app/src/utils/LinphoneEnums.hpp b/linphone-app/src/utils/LinphoneEnums.hpp index 806bf3139..4fed4a75c 100644 --- a/linphone-app/src/utils/LinphoneEnums.hpp +++ b/linphone-app/src/utils/LinphoneEnums.hpp @@ -95,8 +95,25 @@ enum ChatMessageState { }; Q_ENUM_NS(ChatMessageState) -linphone::ChatMessage::State toLinphone(const LinphoneEnums::ChatMessageState& capability); -LinphoneEnums::ChatMessageState fromLinphone(const linphone::ChatMessage::State& capability); +linphone::ChatMessage::State toLinphone(const LinphoneEnums::ChatMessageState& data); +LinphoneEnums::ChatMessageState fromLinphone(const linphone::ChatMessage::State& data); + +enum ChatRoomState { + ChatRoomStateNone = int(linphone::ChatRoom::State::None), + ChatRoomStateInstantiated = int(linphone::ChatRoom::State::Instantiated), + ChatRoomStateCreationPending = int(linphone::ChatRoom::State::CreationPending), + ChatRoomStateCreated = int(linphone::ChatRoom::State::Created), + ChatRoomStateCreationFailed = int(linphone::ChatRoom::State::CreationFailed), + ChatRoomStateTerminationPending = int(linphone::ChatRoom::State::TerminationPending), + ChatRoomStateTerminated = int(linphone::ChatRoom::State::Terminated), + ChatRoomStateTerminationFailed = int(linphone::ChatRoom::State::TerminationFailed), + ChatRoomStateDeleted = int(linphone::ChatRoom::State::Deleted), +}; +Q_ENUM_NS(ChatRoomState) + +linphone::ChatRoom::State toLinphone(const LinphoneEnums::ChatRoomState& data); +LinphoneEnums::ChatRoomState fromLinphone(const linphone::ChatRoom::State& data); + enum CallStatus { CallStatusDeclined = int(linphone::Call::Status::Declined), @@ -133,6 +150,18 @@ Q_ENUM_NS(ConferenceInfoState) linphone::ConferenceInfo::State toLinphone(const LinphoneEnums::ConferenceInfoState& state); LinphoneEnums::ConferenceInfoState fromLinphone(const linphone::ConferenceInfo::State& state); +enum ConferenceSchedulerState { + ConferenceSchedulerStateAllocationPending = int(linphone::ConferenceScheduler::State::AllocationPending), + ConferenceSchedulerStateError = int(linphone::ConferenceScheduler::State::Error), + ConferenceSchedulerStateIdle = int(linphone::ConferenceScheduler::State::Idle), + ConferenceSchedulerStateReady = int(linphone::ConferenceScheduler::State::Ready), + ConferenceSchedulerStateUpdating = int(linphone::ConferenceScheduler::State::Updating) +}; +Q_ENUM_NS(ConferenceSchedulerState) + +linphone::ConferenceScheduler::State toLinphone(const LinphoneEnums::ConferenceSchedulerState& state); +LinphoneEnums::ConferenceSchedulerState fromLinphone(const linphone::ConferenceScheduler::State& state); + enum ParticipantDeviceState { ParticipantDeviceStateJoining = int(linphone::ParticipantDeviceState::Joining), @@ -190,14 +219,16 @@ void fromString(const QString& transportType, LinphoneEnums::TransportType *tran Q_DECLARE_METATYPE(LinphoneEnums::CallStatus) Q_DECLARE_METATYPE(LinphoneEnums::ChatMessageState) +Q_DECLARE_METATYPE(LinphoneEnums::ChatRoomState) Q_DECLARE_METATYPE(LinphoneEnums::ConferenceLayout) Q_DECLARE_METATYPE(LinphoneEnums::ConferenceInfoState) +Q_DECLARE_METATYPE(LinphoneEnums::ConferenceSchedulerState) Q_DECLARE_METATYPE(LinphoneEnums::EventLogType) Q_DECLARE_METATYPE(LinphoneEnums::FriendCapability) Q_DECLARE_METATYPE(LinphoneEnums::MediaEncryption) Q_DECLARE_METATYPE(LinphoneEnums::ParticipantDeviceState) Q_DECLARE_METATYPE(LinphoneEnums::RecorderState) Q_DECLARE_METATYPE(LinphoneEnums::TunnelMode) - +Q_DECLARE_METATYPE(LinphoneEnums::TransportType) #endif diff --git a/linphone-app/ui/modules/Common/Form/Mosaic.qml b/linphone-app/ui/modules/Common/Form/Mosaic.qml index 32151bbc5..48ea6f1bb 100644 --- a/linphone-app/ui/modules/Common/Form/Mosaic.qml +++ b/linphone-app/ui/modules/Common/Form/Mosaic.qml @@ -18,7 +18,7 @@ ColumnLayout{ } function add(item){ - if( !grid.isLayoutWillChanged() || !transitionningTimer.running) + if( !grid.isLayoutWillChanged()) appendItem(item) else bufferModels.append(item) @@ -34,7 +34,7 @@ ColumnLayout{ } function tryToAdd(item){ - if( !grid.isLayoutWillChanged() || !transitionningTimer.running) { + if( !grid.isLayoutWillChanged()) { appendItem(item) return true }else @@ -51,21 +51,7 @@ ColumnLayout{ property int transitionCount : 0 property var bufferModels : ListModel{} - property int maxTransitionTime: 250 - Timer{ - id: transitionningTimer - running: false - interval: maxTransitionTime + 5 - onTriggered: updateBuffers() - } - function startTransition(){ - transitionningTimer.restart() - } - function updateBuffers(){ - while(bufferModels.count > 0 && tryToAdd(bufferModels.get(0))){ - bufferModels.remove(0,1) - } - } + onWidthChanged: grid.updateLayout() onHeightChanged: grid.updateLayout() GridView{ @@ -120,63 +106,6 @@ ColumnLayout{ interactive: false model: DelegateModel{} - //------------------- ANIMATIONS - property Transition defaultTransition: Transition { - SequentialAnimation { - ScriptAction { - script: { - mainLayout.startTransition() - } - } - ParallelAnimation { - NumberAnimation { properties: "x,y"; duration: mainLayout.maxTransitionTime } - } - } - } - - add: Transition { - SequentialAnimation { - ScriptAction { - script: { - mainLayout.startTransition() - } - } - ParallelAnimation { - NumberAnimation { property: "opacity"; from: 0; duration: mainLayout.maxTransitionTime } - NumberAnimation { properties: "x,y"; from: 0; duration: mainLayout.maxTransitionTime; easing.type: Easing.OutBounce } - } - } - } - - addDisplaced: defaultTransition - displaced: defaultTransition - move: defaultTransition - moveDisplaced: defaultTransition - remove: Transition { - SequentialAnimation { - PropertyAction { target: grid; property: "GridView.delayRemove"; value: true } - ScriptAction { - script: { - mainLayout.startTransition() - } - } - ParallelAnimation { - NumberAnimation { property: "opacity"; to: 0; duration: mainLayout.maxTransitionTime } - NumberAnimation { properties: "x,y"; to: 0; duration: mainLayout.maxTransitionTime } - } - PropertyAction { target: grid; property: "GridView.delayRemove"; value: false } - } - } - removeDisplaced: defaultTransition - populate:defaultTransition - - Timer{ // if cell sizes change while adding/removing an item the animation will not end at the right position. - id: updateLayoutDelay - interval: mainLayout.maxTransitionTime - onTriggered: grid.updateLayout() - } - onItemCountChanged: { - updateLayoutDelay.restart() - } + onCountChanged: grid.updateLayout() } } \ No newline at end of file diff --git a/linphone-app/ui/modules/Common/Form/RadioButton.qml b/linphone-app/ui/modules/Common/Form/RadioButton.qml index 8b27a6d10..449df7852 100644 --- a/linphone-app/ui/modules/Common/Form/RadioButton.qml +++ b/linphone-app/ui/modules/Common/Form/RadioButton.qml @@ -17,6 +17,14 @@ Control.RadioButton{ font.pointSize: RadioButtonStyle.pointSize spacing: 10 FontMetrics{id: fontMetrics} + + MouseArea{ + anchors.fill:parent + hoverEnabled: true + acceptedButtons: Qt.NoButton + cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor + } + indicator: Rectangle { height: fontMetrics.height - 5 width: height diff --git a/linphone-app/ui/modules/Common/Picker/DatePicker.qml b/linphone-app/ui/modules/Common/Picker/DatePicker.qml index fb9eb1b0d..0d816ac55 100644 --- a/linphone-app/ui/modules/Common/Picker/DatePicker.qml +++ b/linphone-app/ui/modules/Common/Picker/DatePicker.qml @@ -5,6 +5,8 @@ import Common 1.0 import Common.Styles 1.0 import Units 1.0 +import 'qrc:/ui/scripts/Utils/utils.js' as Utils + Item{ id: mainItem property bool allYears : false // if false : years from today @@ -33,9 +35,10 @@ Item{ Layout.fillWidth: true Layout.alignment: Qt.AlignCenter horizontalAlignment: Qt.AlignCenter - text: new Date(monthList.currentYear, monthList.currentMonth, 1).toLocaleString(Qt.locale(), 'MMMM yyyy') + text: new Date(monthList.currentYear, monthList.currentMonth, 15).toLocaleString(Qt.locale(), 'MMMM yyyy')// 15 because of timezones that can change the date for localeString color: DatePickerStyle.title.color font.pointSize: DatePickerStyle.title.pointSize + font.capitalization: Font.Capitalize } ActionButton{ isCustom: true @@ -62,7 +65,7 @@ Item{ } property date selectedDate: new Date() - property int minYear: mainItem.allYears ? new Date(0,0,0).getFullYear() : new Date().getFullYear() + property int minYear: mainItem.allYears ? new Date(0,0,1).getFullYear() : new Date().getFullYear() snapMode: ListView.SnapOneItem orientation: Qt.Horizontal @@ -70,7 +73,7 @@ Item{ // One model per month model: (new Date().getFullYear()- minYear + maxYears) * 12 - + currentIndex: 0 property int currentYear: Math.floor(currentIndex / 12) + minYear property int currentMonth: currentIndex % 12 @@ -87,7 +90,7 @@ Item{ property int year: Math.floor(index / 12) + monthList.minYear property int month: index % 12 property int firstDay: new Date(year, month, 1).getDay() - + GridLayout { // 1 month calender id: grid @@ -105,9 +108,12 @@ Item{ delegate: Item{ id: cellItem - property int day: index - 7 // 0 = top left below Sunday (-7 to 41) + property int day: index - 7 // 0 = top left below Sunday (-7 to 41) property int date: day - firstDay + 1 // 1-31 - property bool selected : new Date(year, month, date).toDateString() == monthList.selectedDate.toDateString() && text.text && day >= 0 + property date cellDate: new Date(year, month, date) + property bool selected : text.text != '-' + && Utils.equalDate(cellDate, monthList.selectedDate) + && text.text && day >= 0 width: grid.cellMinSize height: width @@ -132,12 +138,12 @@ Item{ : cellItem.day < 0 ? DatePickerStyle.cell.dayHeaderPointSize : DatePickerStyle.cell.dayPointSize - font.bold: cellItem.day < 0 || cellItem.selected || new Date(year, month, cellItem.date).toDateString() == new Date().toDateString() // today + font.bold: cellItem.day < 0 || cellItem.selected || Utils.equalDate(cellItem.cellDate, new Date()) // today text: { - if(cellItem.day < 0) + if(cellItem.day < 0){ // Magic date to set day names in this order : 'S', 'M', 'T', 'W', 'T', 'F', 'S' in Locale - return new Date(1,3,index).toLocaleString(Qt.locale(), 'ddd')[0] - else if(new Date(year, month, cellItem.date).getMonth() == month && (!hideOldDates || new Date(year, month, cellItem.date+1) >= new Date())) // new Date use time too + return Utils.exactDate(new Date(2000,9,index+1)).toLocaleString(Qt.locale(), 'ddd')[0].toUpperCase() + }else if(cellItem.cellDate.getMonth() == month && (!hideOldDates || new Date(year, month, cellItem.date+1) >= new Date())) // new Date use time too return cellItem.date else return '-' @@ -152,7 +158,7 @@ Item{ enabled: text.text && text.text != '-' && cellItem.day >= 0 onClicked: { - monthList.selectedDate = new Date(year, month, cellItem.date) + monthList.selectedDate = cellItem.cellDate mainItem.clicked(monthList.selectedDate) } } diff --git a/linphone-app/ui/modules/Common/Styles/Animations/BusyIndicatorStyle.qml b/linphone-app/ui/modules/Common/Styles/Animations/BusyIndicatorStyle.qml index 40b95d07a..083f6d463 100644 --- a/linphone-app/ui/modules/Common/Styles/Animations/BusyIndicatorStyle.qml +++ b/linphone-app/ui/modules/Common/Styles/Animations/BusyIndicatorStyle.qml @@ -6,6 +6,7 @@ import ColorsList 1.0 QtObject { property string sectionName: 'Busy' property color color: ColorsList.add(sectionName+'_indicator', 'q').color + property color alternateColor: ColorsList.add(sectionName+'_indicator_alt', 'i').color property int duration: 1250 property int nSpheres: 8 } diff --git a/linphone-app/ui/modules/Linphone/Calls/CallControls.qml b/linphone-app/ui/modules/Linphone/Calls/CallControls.qml index 1797322f5..9687bdf4e 100644 --- a/linphone-app/ui/modules/Linphone/Calls/CallControls.qml +++ b/linphone-app/ui/modules/Linphone/Calls/CallControls.qml @@ -13,6 +13,7 @@ Rectangle { // --------------------------------------------------------------------------- default property alias _content: content.data + property alias isDarkMode: contact.isDarkMode property alias signIcon: signIcon.icon property alias subtitleColor: contact.subtitleColor diff --git a/linphone-app/ui/modules/Linphone/Calls/Calls.qml b/linphone-app/ui/modules/Linphone/Calls/Calls.qml index 4ce7f47f5..5004e78d5 100644 --- a/linphone-app/ui/modules/Linphone/Calls/Calls.qml +++ b/linphone-app/ui/modules/Linphone/Calls/Calls.qml @@ -144,20 +144,17 @@ ListView { id: _callControls // ------------------------------------------------------------------------- - - function useColorStatus () { - return calls.currentIndex === index && $modelData && $modelData.status !== CallModel.CallStatusEnded - } + isDarkMode: calls.currentIndex === index && $modelData!=undefined && $modelData.status!=undefined && $modelData.status !== CallModel.CallStatusEnded // ------------------------------------------------------------------------- - color: useColorStatus() + color: isDarkMode ? CallsStyle.entry.color.selected : CallsStyle.entry.color.normal - subtitleColor: useColorStatus() + subtitleColor: isDarkMode ? CallsStyle.entry.subtitleColor.selected : CallsStyle.entry.subtitleColor.normal - titleColor: useColorStatus() + titleColor: isDarkMode ? CallsStyle.entry.titleColor.selected : CallsStyle.entry.titleColor.normal diff --git a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml index dcb1ea77d..7e0d04d3b 100644 --- a/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml +++ b/linphone-app/ui/modules/Linphone/Camera/CameraItem.qml @@ -22,10 +22,11 @@ Item { property bool isFullscreen: false property bool hideCamera: false property bool isPaused: false - property bool deactivateCamera: false + property bool deactivateCamera: true property bool isVideoEnabled: !deactivateCamera && (!callModel || callModel.videoEnabled) - && (!container.currentDevice || callModel && (container.currentDevice - && (container.currentDevice.videoEnabled || (container.currentDevice.isMe && callModel.cameraEnabled)))) + && (!container.currentDevice || ( callModel && container.currentDevice && + ( (!container.currentDevice.isMe && container.currentDevice.videoEnabled) + || (container.currentDevice.isMe && callModel.cameraEnabled)))) property bool a : callModel && callModel.videoEnabled property bool b: container.currentDevice && container.currentDevice.videoEnabled @@ -51,6 +52,10 @@ Item { anchors.fill: parent active: !resetActive && container.isVideoEnabled + onActiveChanged: { + console.log("QML Camera status : " + active) + } + sourceComponent: container.isVideoEnabled && !container.isPaused? camera : null Timer{ diff --git a/linphone-app/ui/modules/Linphone/Camera/CameraView.qml b/linphone-app/ui/modules/Linphone/Camera/CameraView.qml index 42cd2e201..9d1e00139 100644 --- a/linphone-app/ui/modules/Linphone/Camera/CameraView.qml +++ b/linphone-app/ui/modules/Linphone/Camera/CameraView.qml @@ -79,6 +79,7 @@ Item{ anchors.fill: parent visible: false onVideoDefinitionChanged: mainItem.videoDefinitionChanged() + deactivateCamera: false } OpacityMask{ id: renderedCamera diff --git a/linphone-app/ui/modules/Linphone/Chat/Chat.qml b/linphone-app/ui/modules/Linphone/Chat/Chat.qml index 47c3b769b..3e8c4a35e 100644 --- a/linphone-app/ui/modules/Linphone/Chat/Chat.qml +++ b/linphone-app/ui/modules/Linphone/Chat/Chat.qml @@ -12,6 +12,7 @@ import LinphoneEnums 1.0 import Units 1.0 import 'Chat.js' as Logic +import 'qrc:/ui/scripts/Utils/utils.js' as Utils // ============================================================================= @@ -32,10 +33,17 @@ Rectangle { color: ChatStyle.color clip: true - + Timer{// Let some time to have a better cell sizes + id: repositionerDelay + property int indexToMove + interval: 100 + onTriggered: chat.positionViewAtIndex(indexToMove, ListView.Center) + } function positionViewAtIndex(index){ chat.bindToEnd = false - chat.positionViewAtIndex(index, ListView.Beginning) + chat.positionViewAtIndex(index, ListView.Center) + repositionerDelay.indexToMove = index + repositionerDelay.restart() } function goToMessage(message){ @@ -290,10 +298,10 @@ Rectangle { //: 'Choose where to forward the message' : Dialog title for choosing where to forward the current message. , {title: qsTr('forwardDialogTitle'), addressSelectedCallback: function (sipAddress) { - var chat = CallsListModel.createChat(sipAddress) + var chat = CallsListModel.createChatRoom( '', proxyModel.chatRoomModel.haveEncryption, [sipAddress], false ) if(chat){ - chat.forwardMessage($chatEntry) - TimelineListModel.select(chat) + chat.chatRoomModel.forwardMessage($chatEntry) + TimelineListModel.select(chat.chatRoomModel) } }, chatRoomSelectedCallback: function (chatRoomModel){ @@ -379,6 +387,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: textAreaBorders.height + chatMessagePreview.height+messageBlock.height color: ChatStyle.sendArea.backgroundBorder.color + visible: proxyModel.chatRoomModel && !proxyModel.chatRoomModel.isReadOnly && (!proxyModel.chatRoomModel.haveEncryption && SettingsModel.standardChatEnabled || proxyModel.chatRoomModel.haveEncryption && SettingsModel.secureChatEnabled) ColumnLayout{ anchors.fill: parent spacing: 0 @@ -411,7 +420,6 @@ Rectangle { Layout.leftMargin: ChatStyle.sendArea.backgroundBorder.width borderColor: ChatStyle.sendArea.border.color topWidth: ChatStyle.sendArea.border.width - visible: proxyModel.chatRoomModel && !proxyModel.chatRoomModel.isReadOnly && (!proxyModel.chatRoomModel.haveEncryption && SettingsModel.standardChatEnabled || proxyModel.chatRoomModel.haveEncryption && SettingsModel.secureChatEnabled) DroppableTextArea { id: textArea @@ -423,7 +431,7 @@ Rectangle { anchors.right: parent.right anchors.bottom: parent.bottom - height:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width + height: visible ? ChatStyle.sendArea.height + ChatStyle.sendArea.border.width : 0 minimumHeight:ChatStyle.sendArea.height + ChatStyle.sendArea.border.width maximumHeight:container.height/2 @@ -445,10 +453,7 @@ Rectangle { } } onAudioRecordRequest: RecorderManager.resetVocalRecorder() - Component.onCompleted: { - text = proxyModel.chatRoomModel.cachedText - cursorPosition=text.length - } + Component.onCompleted: {text = proxyModel.cachedText; cursorPosition=text.length} Rectangle{ anchors.fill:parent color:'white' diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatCalendarMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatCalendarMessage.qml index 4f8be65ec..85373bb74 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatCalendarMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatCalendarMessage.qml @@ -111,7 +111,8 @@ Loader{ color: ChatCalendarMessageStyle.schedule.color elide: Text.ElideRight font.pointSize: ChatCalendarMessageStyle.schedule.pointSize - text: Qt.formatDateTime(mainItem.conferenceInfoModel.dateTime, 'hh:mm') +// Reminder: QML use locale time (not system). Use UTC from C++ => convert it into QML => pass QML => convert it into UTC and apply our timezone. + text: UtilsCpp.toTimeString(Utils.fromUTC(mainItem.conferenceInfoModel.dateTimeUtc), 'hh:mm') + (mainItem.conferenceInfoModel.duration > 0 ? ' (' +Utils.formatDuration(mainItem.conferenceInfoModel.duration * 60) + ')' : '') } @@ -143,10 +144,10 @@ Loader{ color: ChatCalendarMessageStyle.type.cancelledColor font.pointSize: ChatCalendarMessageStyle.type.pointSize font.weight: Font.Bold - text: 'You have cancelled the conference' + //: 'Meeting has been cancelled' : ICS Title for cancelled meetings + text:qsTr('icsCancelledMeetingInvite') } - Text{ id: title Layout.fillWidth: true @@ -178,6 +179,8 @@ Loader{ Layout.preferredHeight: parent.participantLineHeight Layout.preferredWidth: ChatCalendarMessageStyle.participants.iconSize Layout.alignment: Qt.AlignTop + + visible: mainItem.conferenceInfoModel.participantCount > 0 clip: false Icon{ anchors.top: parent.top @@ -209,7 +212,11 @@ Loader{ Layout.alignment: Qt.AlignTop spacing: 0 visible: mainItem.isExpanded - onVisibleChanged: model= mainItem.conferenceInfoModel.getParticipants() + onVisibleChanged: visible ? model= mainItem.conferenceInfoModel.getAllParticipants() : model = [] + Connections{ + target: mainItem.conferenceInfoModel + onParticipantsChanged: if(expandedParticipantsList.visible) expandedParticipantsList.model = mainItem.conferenceInfoModel.getAllParticipants() + } delegate: Row{ spacing: 5 diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml index e9820c144..750bf9de1 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatConferenceInvitationMessage.qml @@ -109,12 +109,14 @@ Loader{ } RowLayout { id: participantsRow + property int participantCount: mainItem.conferenceInfoModel.allParticipantCount Layout.fillWidth: true Layout.preferredHeight: ChatCalendarMessageStyle.participants.iconSize Layout.leftMargin: 5 Layout.rightMargin: 15 - spacing: ChatCalendarMessageStyle.participants.spacing + spacing: ChatCalendarMessageStyle.participants.spacing + visible: participantsRow.participantCount > 0 Icon{ icon: ChatCalendarMessageStyle.participants.icon @@ -124,13 +126,12 @@ Loader{ Text { id: participantsList - property int participantCount: mainItem.conferenceInfoModel.getParticipantCount() Layout.fillWidth: true color: ChatCalendarMessageStyle.participants.color elide: Text.ElideRight font.pointSize: ChatCalendarMessageStyle.participants.pointSize //: '%1 participant' : number(=%1) of participant. - text: qsTr('icsParticipants', '', participantCount).arg(participantCount) + text: qsTr('icsParticipants', '', participantsRow.participantCount).arg(participantsRow.participantCount) } } ColumnLayout{ @@ -158,7 +159,7 @@ Loader{ color: ChatCalendarMessageStyle.schedule.color elide: Text.ElideRight font.pointSize: Units.dp * 8 - text: Qt.formatDate(mainItem.conferenceInfoModel.dateTimeUtc, 'yyyy/MM/dd') + text: UtilsCpp.toDateString(Utils.fromUTC(mainItem.conferenceInfoModel.dateTimeUtc), 'yyyy/MM/dd') } } RowLayout { @@ -182,7 +183,8 @@ Loader{ color: ChatCalendarMessageStyle.schedule.color elide: Text.ElideRight font.pointSize: Units.dp * 8 - text: Qt.formatDateTime(mainItem.conferenceInfoModel.dateTimeUtc, 'hh:mm') +// Reminder: QML use locale time (not system). Use UTC from C++ => convert it into QML => pass QML => convert it into UTC and apply our timezone. + text: UtilsCpp.toTimeString( Utils.fromUTC(mainItem.conferenceInfoModel.dateTimeUtc), 'hh:mm') + (mainItem.conferenceInfoModel.duration > 0 ? ' ('+Utils.formatDuration(mainItem.conferenceInfoModel.duration * 60) + ')' : '') } @@ -202,7 +204,11 @@ Loader{ Layout.leftMargin: 10 spacing: 0 visible: mainItem.isExpanded - onVisibleChanged: model= mainItem.conferenceInfoModel.getParticipants() + onVisibleChanged: visible ? model= mainItem.conferenceInfoModel.getAllParticipants() : model = [] + Connections{ + target: mainItem.conferenceInfoModel + onParticipantsChanged: if(expandedParticipantsList.visible) expandedParticipantsList.model = mainItem.conferenceInfoModel.getAllParticipants() + } delegate: Row{ spacing: 5 diff --git a/linphone-app/ui/modules/Linphone/Contact/Avatar.qml b/linphone-app/ui/modules/Linphone/Contact/Avatar.qml index ab2f99fcf..a1a06c7a8 100644 --- a/linphone-app/ui/modules/Linphone/Contact/Avatar.qml +++ b/linphone-app/ui/modules/Linphone/Contact/Avatar.qml @@ -10,84 +10,95 @@ import UtilsCpp 1.0 // ============================================================================= Item { - id: avatar - - // --------------------------------------------------------------------------- - - property alias presenceLevel: presenceLevelIcon.level - property color backgroundColor: AvatarStyle.backgroundColor - property color foregroundColor: 'transparent' - property string username - property var image - - property var _initialsRegex: /^\s*([^\s\.]+)(?:[\s\.]+([^\s\.]+))?/ - - property bool isPhoneNumber: UtilsCpp.isPhoneNumber(username) - - // --------------------------------------------------------------------------- - - function isLoaded () { - return roundedImage.status === Image.Ready - } - - function _computeInitials () { - var result = username.match(_initialsRegex) - if (!result) { - return username.length > 0 ? username.charAt(0).toUpperCase() : '' - } - - return result[1].charAt(0).toUpperCase() + ( - result[2] != null - ? result[2].charAt(0).toUpperCase() - : '' - ) - } - - // --------------------------------------------------------------------------- - - RoundedImage { - id: roundedImage - - anchors.fill: parent - backgroundColor: avatar.backgroundColor - foregroundColor: avatar.foregroundColor - source: avatar.image || '' - Icon{ + id: avatar + + // --------------------------------------------------------------------------- + + property alias presenceLevel: presenceLevelIcon.level + property bool isDarkMode: false + property color backgroundColor: isDarkMode ? AvatarStyle.backgroundDarkModeColor : AvatarStyle.backgroundColor + property color foregroundColor: 'transparent' + property string username + property var image + property bool isOneToOne: true + + property var _initialsRegex: /^\s*([^\s\.]+)(?:[\s\.]+([^\s\.]+))?/ + + property bool isPhoneNumber: UtilsCpp.isPhoneNumber(username) + + // --------------------------------------------------------------------------- + + function isLoaded () { + return roundedImage.status === Image.Ready + } + + function _computeInitials () { + var result = username.match(_initialsRegex) + if (!result) { + return username.length > 0 ? username.charAt(0).toUpperCase() : '' + } + + return result[1].charAt(0).toUpperCase() + ( + result[2] != null + ? result[2].charAt(0).toUpperCase() + : '' + ) + } + + // --------------------------------------------------------------------------- + + RoundedImage { + id: roundedImage + anchors.fill: parent - icon: AvatarStyle.personImage - visible: parent.source == '' && avatar.isPhoneNumber - overwriteColor: AvatarStyle.initials.color - } - } - - Text { - id: initialsText - anchors.centerIn: parent - color: AvatarStyle.initials.color - font.pointSize: { - var width - - if (parent.width > 0) { - width = parent.width / AvatarStyle.initials.ratio - } - - return AvatarStyle.initials.pointSize * (width || 1) - } - - text: _computeInitials() - visible: roundedImage.status !== Image.Ready && !avatar.isPhoneNumber - } - - PresenceLevel { - id: presenceLevelIcon - visible: level >= 0 - - anchors { - bottom: parent.bottom - right: parent.right - } - - height: parent.height / 4 - width: parent.width / 4 - } + backgroundColor: avatar.backgroundColor + foregroundColor: avatar.foregroundColor + source: avatar.image || '' + Icon{ + anchors.fill: parent + icon: AvatarStyle.personImage + visible: parent.source == '' && avatar.isPhoneNumber + overwriteColor: AvatarStyle.initials.color + } + } + + Text { + id: initialsText + anchors.centerIn: parent + color: isDarkMode ? AvatarStyle.initials.darkModeColor : AvatarStyle.initials.color + font.pointSize: { + var width + + if (parent.width > 0) { + width = parent.width / AvatarStyle.initials.ratio + } + + return AvatarStyle.initials.pointSize * (width || 1) + } + + text: _computeInitials() + visible: roundedImage.status !== Image.Ready && !avatar.isPhoneNumber && avatar.isOneToOne + } + + Icon { + anchors.fill: parent + icon: ContactStyle.groupChat.icon + overwriteColor: isDarkMode ? ContactStyle.groupChat.avatarDarkModeColor : ContactStyle.groupChat.avatarColor + iconSize: avatar.width + //visible: entry!=undefined && entry.isOneToOne!=undefined && !entry.isOneToOne + visible: !avatar.isOneToOne + } + + PresenceLevel { + id: presenceLevelIcon + visible: level >= 0 + + anchors { + bottom: parent.bottom + right: parent.right + } + + height: parent.height / 4 + width: parent.width / 4 + } } diff --git a/linphone-app/ui/modules/Linphone/Contact/Contact.qml b/linphone-app/ui/modules/Linphone/Contact/Contact.qml index 81a7a73f2..5dcc99f93 100644 --- a/linphone-app/ui/modules/Linphone/Contact/Contact.qml +++ b/linphone-app/ui/modules/Linphone/Contact/Contact.qml @@ -21,9 +21,11 @@ Rectangle { property alias subtitleColor: description.subtitleColor property alias titleColor: description.titleColor property alias statusText : description.statusText + property alias isDarkMode: avatar.isDarkMode property bool displayUnreadMessageCount: false property bool showSubtitle : true + property bool showBusyIndicator: false property string subtitle: '' property string subject: (entry && entry.conferenceInfoModel && entry.conferenceInfoModel.subject @@ -75,16 +77,7 @@ Rectangle { ? '' : item.username : item.username - visible:!groupChat.visible - Icon { - - anchors.fill: parent - - icon: ContactStyle.groupChat.icon - overwriteColor: ContactStyle.groupChat.avatarColor - iconSize: ContactStyle.contentHeight - visible: entry!=undefined && entry.isOneToOne!=undefined && !entry.isOneToOne - } + isOneToOne: entry==undefined || entry.isOneToOne==undefined || entry.isOneToOne Icon{ anchors.top:parent.top @@ -97,25 +90,26 @@ Rectangle { anchors.fill: parent onClicked: item.avatarClicked(mouse) } - } - Icon { - id: groupChat - Layout.preferredHeight: ContactStyle.contentHeight - Layout.preferredWidth: ContactStyle.contentHeight - - icon: ContactStyle.groupChat.icon - overwriteColor: ContactStyle.groupChat.color - iconSize: ContactStyle.contentHeight - visible: false //entry!=undefined && entry.isOneToOne!=undefined && !entry.isOneToOne - - Icon{ - anchors.right: parent.right - anchors.top:parent.top - anchors.topMargin: -5 - visible: entry!=undefined && entry.haveEncryption != undefined && entry.haveEncryption - icon: entry?(entry.securityLevel === 2?'secure_level_1': entry.securityLevel===3? 'secure_level_2' : 'secure_level_unsafe'):'secure_level_unsafe' - iconSize:15 + Loader{ + id: busyLoader + + anchors.fill: parent + anchors.margins: 5 + + active: item.showBusyIndicator + sourceComponent: Component{ + BusyIndicator{// Joining spinner + id: joiningSpinner + running: false + Timer{// Delay starting spinner (Qt bug) + id: indicatorDelay + interval: 100 + onTriggered: joiningSpinner.running = true + } + Component.onCompleted: indicatorDelay.start() + } + } } } @@ -136,7 +130,9 @@ Rectangle { ? item.organizer ? item.organizer : entry.sipAddress || entry.fullPeerAddress || entry.peerAddress || '' - : entry.participants.addressesToString + : entry.participants + ? entry.participants.addressesToString + : '' : '' } diff --git a/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml b/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml index 5a4a550f7..3a1bf0529 100644 --- a/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml +++ b/linphone-app/ui/modules/Linphone/Menus/IncallMenu.qml @@ -50,6 +50,14 @@ Rectangle{ onVisibleChanged: if(!visible && contentsStack.nViews > 1) { contentsStack.pop() } + property bool _activateCamera: false + Connections{// Enable camera only when status is ok + target: mainItem.callModel + onStatusChanged: if( mainItem._activateCamera && (status == LinphoneEnums.CallStatusConnected || status == LinphoneEnums.CallStatusIdle)){ + camera._activateCamera = false + callModel.cameraEnabled = true + } + } ButtonGroup{id: modeGroup} ColumnLayout{ anchors.fill: parent @@ -158,13 +166,16 @@ Rectangle{ font.pointSize: IncallMenuStyle.list.pointSize color: IncallMenuStyle.list.color } - ActionButton{ + Icon{ Layout.minimumWidth: iconWidth Layout.rightMargin: 10 Layout.alignment: Qt.AlignVCenter - backgroundRadius: width/2 - isCustom: true - colorSet: IncallMenuStyle.buttons.next + //backgroundRadius: width/2 + + icon: IncallMenuStyle.buttons.next.icon + overwriteColor: IncallMenuStyle.buttons.next.backgroundNormalColor + iconWidth: IncallMenuStyle.buttons.next.iconSize + iconHeight: IncallMenuStyle.buttons.next.iconSize } } MouseArea{ @@ -227,6 +238,11 @@ Rectangle{ bottomWidth: IncallMenuStyle.list.border.width Layout.preferredHeight: Math.max(layoutIcon.height, radio.contentItem.implicitHeight) + 20 Layout.fillWidth: true + enabled: mainItem.callModel && !mainItem.callModel.updating + MouseArea{ + anchors.fill: parent + onClicked: radio.clicked() + } RowLayout{ anchors.fill: parent @@ -238,37 +254,16 @@ Rectangle{ Layout.alignment: Qt.AlignVCenter ButtonGroup.group: modeGroup text: modelData.text - property bool isInternallyChecked: mainItem.callModel ? (mainItem.callModel.localVideoEnabled && modelData.value == mainItem.callModel.conferenceVideoLayout) || (!mainItem.callModel.localVideoEnabled && modelData.value == LinphoneEnums.ConferenceLayoutAudioOnly) : false // break bind. Radiobutton checked itself without taking care of custom binding. This workaound works as long as we don't really need the binding. onIsInternallyCheckedChanged: checked = isInternallyChecked Component.onCompleted: checked = isInternallyChecked - Timer{ - id: changingLayoutDelay - interval: 100 - onTriggered: {if(modelData.value == 2) mainItem.callModel.videoEnabled = false - else { - mainItem.callModel.conferenceVideoLayout = modelData.value - mainItem.callModel.videoEnabled = true - } - mainItem.enabled = true - } - } - onClicked:{ - // Do changes only if we choose a different layout. - if(! ( mainItem.callModel ? (mainItem.callModel.localVideoEnabled && modelData.value == mainItem.callModel.conferenceVideoLayout) - || (!mainItem.callModel.localVideoEnabled && modelData.value == LinphoneEnums.ConferenceLayoutAudioOnly) - : false)){ - mainItem.enabled = false - mainItem.layoutChanging(modelData.value)// Let time to clear cameras - changingLayoutDelay.start() - } - } + onClicked: mainItem.layoutChanging(modelData.value) } Icon{ - id: layoutIcon + id: layoutIcon Layout.minimumWidth: iconWidth Layout.rightMargin: 10 Layout.alignment: Qt.AlignVCenter diff --git a/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml b/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml index 4e7410d7c..f45b33449 100644 --- a/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml +++ b/linphone-app/ui/modules/Linphone/Sticker/DecorationSticker.qml @@ -101,15 +101,20 @@ Item{ color: "#80000000" source: usernameItem } - ActionButton{ - visible: mainItem._showCloseButton && mainItem._isPreview && mainItem._callModel && mainItem._callModel.videoEnabled + Loader{ + id: closeLoader anchors.right: parent.right anchors.top: parent.top anchors.rightMargin: 5 anchors.topMargin: 5 - isCustom: true - colorSet: DecorationStickerStyle.closePreview - onClicked: mainItem.closeRequested() + active: mainItem._showCloseButton && mainItem._isPreview && mainItem._callModel && mainItem._callModel.videoEnabled + sourceComponent: Component{ + ActionButton{ + isCustom: true + colorSet: DecorationStickerStyle.closePreview + onClicked: mainItem.closeRequested() + } + } } ColumnLayout{ anchors.top: parent.top @@ -137,17 +142,24 @@ Item{ iconSize: DecorationStickerStyle.isMuted.button.iconSize } } - BusyIndicator{// Joining spinner + Loader{ + id: busyLoader + Layout.preferredHeight: 20 Layout.preferredWidth: 20 - property bool delayed : false - visible: delayed && mainItem._currentDevice && (mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateScheduledForJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateAlerting) - Timer{// Delay starting spinner (Qt bug) - id: indicatorDelay - interval: 100 - onTriggered: parent.delayed = true + active: mainItem._currentDevice && (mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateScheduledForJoining || mainItem._currentDevice.state == LinphoneEnums.ParticipantDeviceStateAlerting) + sourceComponent: Component{ + BusyIndicator{// Joining spinner + id: joiningSpinner + running: false + Timer{// Delay starting spinner (Qt bug) + id: indicatorDelay + interval: 100 + onTriggered: joiningSpinner.running = true + } + Component.onCompleted: indicatorDelay.start() + } } - Component.onCompleted: indicatorDelay.start() } } } diff --git a/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml b/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml index 1cd2bee2f..7b9dae4d5 100644 --- a/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml +++ b/linphone-app/ui/modules/Linphone/Sticker/Sticker.qml @@ -35,7 +35,7 @@ Item{ property alias showActiveSpeakerOverlay: camera.showActiveSpeakerOverlay property alias isCameraFromDevice: camera.isCameraFromDevice property alias deactivateCamera: camera.deactivateCamera - property alias isVideoEnabled: camera.isVideoEnabled + readonly property alias isVideoEnabled: camera.isVideoEnabled property alias image: avatar.image property alias avatarBackgroundColor: avatar.avatarBackgroundColor diff --git a/linphone-app/ui/modules/Linphone/Styles/Calls/CallsStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Calls/CallsStyle.qml index 3722411a7..7056af056 100644 --- a/linphone-app/ui/modules/Linphone/Styles/Calls/CallsStyle.qml +++ b/linphone-app/ui/modules/Linphone/Styles/Calls/CallsStyle.qml @@ -19,7 +19,7 @@ QtObject { } property QtObject burgerMenu: QtObject { property string name : 'burgerMenu' - property string icon : 'burger_menu_custom' + property string icon : 'menu_vdots_custom' property int iconSize: 35 property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_bg').color property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_bg').color @@ -30,7 +30,7 @@ QtObject { } property QtObject selectedBurgerMenu: QtObject { property string name : 'selectedBurgerMenu' - property string icon : 'burger_menu_custom' + property string icon : 'menu_vdots_custom' property int iconSize: 35 property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'me_n_b_inv_bg').color property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'me_h_b_inv_bg').color @@ -39,6 +39,7 @@ QtObject { property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'me_h_b_inv_fg').color property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'me_p_b_inv_fg').color } + property QtObject hangup: QtObject { property int iconSize: 35 property string icon : 'hangup_custom' diff --git a/linphone-app/ui/modules/Linphone/Styles/Contact/AvatarStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Contact/AvatarStyle.qml index a7344b858..8f8f9c1d6 100644 --- a/linphone-app/ui/modules/Linphone/Styles/Contact/AvatarStyle.qml +++ b/linphone-app/ui/modules/Linphone/Styles/Contact/AvatarStyle.qml @@ -9,11 +9,14 @@ import ColorsList 1.0 QtObject { property string sectionName: 'Avatar' property color backgroundColor: ColorsList.add(sectionName+'_bg', 'd').color + property color backgroundDarkModeColor: ColorsList.add(sectionName+'_dark_bg', 'q').color + property string personImage : 'contact_custom' property QtObject initials: QtObject { property color color: ColorsList.add(sectionName+'_initials', 'q').color + property color darkModeColor: ColorsList.add(sectionName+'_dark_initials', 'd').color property int pointSize: Units.dp * 10 property int ratio: 30 } diff --git a/linphone-app/ui/modules/Linphone/Styles/Contact/ContactStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Contact/ContactStyle.qml index 0e25378c6..29b751b5d 100644 --- a/linphone-app/ui/modules/Linphone/Styles/Contact/ContactStyle.qml +++ b/linphone-app/ui/modules/Linphone/Styles/Contact/ContactStyle.qml @@ -16,5 +16,6 @@ QtObject { property string icon: 'chat_room_custom' property color color: ColorsList.addImageColor(sectionName+'_groupChat', icon, 'g').color property color avatarColor: ColorsList.addImageColor(sectionName+'_groupChat_onAvatar', icon, 'q').color + property color avatarDarkModeColor: ColorsList.addImageColor(sectionName+'_groupChat_dark_onAvatar', icon, 'd').color } } diff --git a/linphone-app/ui/modules/Linphone/Styles/Timeline/TimelineStyle.qml b/linphone-app/ui/modules/Linphone/Styles/Timeline/TimelineStyle.qml index 1a25f90d3..e21555316 100644 --- a/linphone-app/ui/modules/Linphone/Styles/Timeline/TimelineStyle.qml +++ b/linphone-app/ui/modules/Linphone/Styles/Timeline/TimelineStyle.qml @@ -17,13 +17,6 @@ QtObject { property color selectedTimerColor: ColorsList.addImageColor(sectionName+'_ephemeralTimer_c', icon, 'q').color } - property QtObject draft: QtObject{ - property string icon: 'draft_custom' - property int iconSize : 30 - property color color: ColorsList.addImageColor(sectionName+'_draft', icon, 'ad').color - property color selectedColor: ColorsList.addImageColor(sectionName+'_draft_c', icon, 'q').color - } - property QtObject contact: QtObject { property int height: 60 diff --git a/linphone-app/ui/modules/Linphone/Timeline/TimelineItem.qml b/linphone-app/ui/modules/Linphone/Timeline/TimelineItem.qml index 2b9db1ebc..1f0741eff 100644 --- a/linphone-app/ui/modules/Linphone/Timeline/TimelineItem.qml +++ b/linphone-app/ui/modules/Linphone/Timeline/TimelineItem.qml @@ -44,6 +44,7 @@ Item { NumberAnimation { target: optionsView; property: 'x'; to:optionsView.width; duration: 200;} } ] + enabled: !contactView.showBusyIndicator Contact { @@ -68,32 +69,20 @@ Item { ? TimelineStyle.contact.title.color.selected : TimelineStyle.contact.title.color.normal showSubtitle: mainItem.timelineModel && (mainItem.timelineModel.chatRoomModel && (mainItem.timelineModel.chatRoomModel.isOneToOne || !mainItem.timelineModel.chatRoomModel.isConference)) - TooltipArea { + showBusyIndicator: mainItem.timelineModel && mainItem.timelineModel.updating + TooltipArea { id: contactTooltip text: mainItem.timelineModel && UtilsCpp.toDateTimeString(mainItem.timelineModel.chatRoomModel.lastUpdateTime) isClickable: true } - - Icon{ - id: draft - icon: TimelineStyle.draft.icon - iconSize: visible ? TimelineStyle.draft.iconSize : 0 - overwriteColor: mainItem.timelineModel && mainItem.timelineModel.selected ? TimelineStyle.draft.selectedColor : TimelineStyle.draft.color - anchors.right:parent.right - anchors.bottom:parent.bottom - anchors.bottomMargin: 3 - anchors.rightMargin: 7 - visible: mainItem.timelineModel && mainItem.timelineModel.chatRoomModel.hasDraft - } - Icon{ icon: TimelineStyle.ephemeralTimer.icon iconSize: TimelineStyle.ephemeralTimer.iconSize overwriteColor: mainItem.timelineModel && mainItem.timelineModel.selected ? TimelineStyle.ephemeralTimer.selectedTimerColor : TimelineStyle.ephemeralTimer.timerColor - anchors.right:draft.left + anchors.right:parent.right anchors.bottom:parent.bottom - anchors.bottomMargin: 3 - anchors.rightMargin: draft.visible ? 0 : 7 + anchors.bottomMargin: 7 + anchors.rightMargin: 7 visible: mainItem.timelineModel && mainItem.timelineModel.chatRoomModel.ephemeralEnabled } MouseArea { @@ -173,5 +162,4 @@ Item { } } } - } \ No newline at end of file diff --git a/linphone-app/ui/modules/Linphone/View/ParticipantsListView.qml b/linphone-app/ui/modules/Linphone/View/ParticipantsListView.qml index e76785ca2..d6cfbd9cd 100644 --- a/linphone-app/ui/modules/Linphone/View/ParticipantsListView.qml +++ b/linphone-app/ui/modules/Linphone/View/ParticipantsListView.qml @@ -11,7 +11,7 @@ import Linphone.Styles 1.0 import Units 1.0 import UtilsCpp 1.0 - +import 'qrc:/ui/scripts/Utils/utils.js' as Utils // ============================================================================= ColumnLayout { @@ -114,6 +114,9 @@ ColumnLayout { colorSet: ParticipantsListViewStyle.removeParticipant, secure:0, visible:true, + visibleHandler: function(entry){ + return !UtilsCpp.isMe(entry.sipAddress) + }, //: 'Remove this participant from the list' : Tootltip to explain that the action will lead to remove the participant. tooltipText: qsTr('participantsListRemoveTooltip'), handler: function (entry) { diff --git a/linphone-app/ui/modules/Linphone/View/ParticipantsView.qml b/linphone-app/ui/modules/Linphone/View/ParticipantsView.qml index fff449043..697114204 100644 --- a/linphone-app/ui/modules/Linphone/View/ParticipantsView.qml +++ b/linphone-app/ui/modules/Linphone/View/ParticipantsView.qml @@ -6,6 +6,8 @@ import Linphone 1.0 import Linphone.Styles 1.0 import Common.Styles 1.0 +import 'qrc:/ui/scripts/Utils/utils.js' as Utils + // ============================================================================= ScrollableListView { @@ -120,6 +122,9 @@ ScrollableListView { backgroundRadius: 90 colorSet: modelData.colorSet visible: sipAddressesView.actions[index].visible + && (!sipAddressesView.actions[index].visibleHandler || sipAddressesView.actions[index].visibleHandler({ + sipAddress: sipAddressesView.interpretableSipAddress + })) onClicked: sipAddressesView.actions[index].handler({ sipAddress: sipAddressesView.interpretableSipAddress @@ -253,16 +258,7 @@ ScrollableListView { statusText : showAdminStatus ? getStatus() : '' entry: $modelData - onAvatarClicked: sipAddressesView.entryClicked(parent.entry, index, contactView) - - BusyIndicator{ - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - width:15 - height:15 - running: sipAddressesView.showInvitingIndicator && $modelData.inviting - } } @@ -289,25 +285,48 @@ ScrollableListView { } Repeater { + id: actionsRepeater model: sipAddressesView.actions - ActionButton { - isCustom: true - backgroundRadius: 90 - colorSet: modelData.colorSet + Item{ + height: buttonAction.height + width: buttonAction.width anchors.verticalCenter: parent.verticalCenter - tooltipText: modelData.tooltipText? modelData.tooltipText:'' - visible: sipAddressesView.actions[index].visible - onClicked: { - sipAddressesView.actions[index].handler(contactView.entry) - } - Icon{ - visible: modelData.secure>0 && - (sipAddressesView.actions[index].secureIconVisibleHandler ? sipAddressesView.actions[index].secureIconVisibleHandler({sipAddres:$modelData}) : true) - icon: modelData.secure === 2?'secure_level_2':'secure_level_1' - iconSize: parent.height/2 - anchors.top:parent.top - anchors.horizontalCenter: parent.right + ActionButton { + id: buttonAction + isCustom: true + backgroundRadius: 90 + colorSet: modelData.colorSet + anchors.verticalCenter: parent.verticalCenter + tooltipText: modelData.tooltipText? modelData.tooltipText:'' + visible: sipAddressesView.actions[index].visible && (!sipAddressesView.actions[index].visibleHandler || sipAddressesView.actions[index].visibleHandler(contactView.entry)) + + onClicked: { + sipAddressesView.actions[index].handler(contactView.entry) + } + Icon{ + visible: modelData.secure>0 && + (sipAddressesView.actions[index].secureIconVisibleHandler ? sipAddressesView.actions[index].secureIconVisibleHandler({sipAddres:$modelData}) : true) + icon: modelData.secure === 2?'secure_level_2':'secure_level_1' + iconSize: parent.height/2 + anchors.top:parent.top + anchors.horizontalCenter: parent.right + } + Loader{ + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + height: parent.height - 2 + width: height + + active: index == actionsRepeater.count -1 && sipAddressesView.showInvitingIndicator && contactView.entry && contactView.entry.inviting + + sourceComponent: Component{ + BusyIndicator{ + color: BusyIndicatorStyle.alternateColor + running: true + } + } + } } } } diff --git a/linphone-app/ui/scripts/Utils/utils.js b/linphone-app/ui/scripts/Utils/utils.js index dc4e2f202..3b8c80bc8 100644 --- a/linphone-app/ui/scripts/Utils/utils.js +++ b/linphone-app/ui/scripts/Utils/utils.js @@ -562,6 +562,24 @@ function buildDate(date, time){ dateTime.setSeconds(time.getSeconds()) return dateTime } + +function equalDate(date1, date2){ + return date1.getFullYear() == date2.getFullYear() && date1.getMonth() == date2.getMonth() && date1.getDate() == date2.getDate() +} + +function fromUTC(date){ + return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), + date.getUTCDate(), date.getUTCHours(), + date.getUTCMinutes(), date.getUTCSeconds())); +} +// return EXACTLY what date has been set (not take account of Locale timezones, eg. Date(2000,0,1) will print january and not december if timezone lead there.) +// Use this function for toLocaleString/toLocaleDateString or other +function exactDate(date) { + var timeOffset = date.getTimezoneOffset() * 60000 + var exactDate = new Date(date.valueOf() - timeOffset) + return exactDate +} + // ----------------------------------------------------------------------------- function formatSize (size) { @@ -788,4 +806,4 @@ function printObject(o) { out += p + ': ' + o[p] + '\n'; } return out; -} \ No newline at end of file +} diff --git a/linphone-app/ui/views/App/Calls/CallsWindow.qml b/linphone-app/ui/views/App/Calls/CallsWindow.qml index 38a188c9f..5a2b81b69 100644 --- a/linphone-app/ui/views/App/Calls/CallsWindow.qml +++ b/linphone-app/ui/views/App/Calls/CallsWindow.qml @@ -140,11 +140,12 @@ Window { ActionButton { isCustom: true backgroundRadius: 4 - colorSet: CallsWindowStyle.callsList.newConference + colorSet: CallsWindowStyle.callsList.mergeConference visible: SettingsModel.conferenceEnabled + enabled: CallsListModel.canMergeCalls onClicked: { - Logic.openConferenceManager() + CallsListModel.mergeAll() } } } diff --git a/linphone-app/ui/views/App/Calls/Incall.qml b/linphone-app/ui/views/App/Calls/Incall.qml index b9f4a2771..9b0bec8f0 100644 --- a/linphone-app/ui/views/App/Calls/Incall.qml +++ b/linphone-app/ui/views/App/Calls/Incall.qml @@ -27,6 +27,7 @@ Rectangle { property bool cameraIsReady : false property bool previewIsReady : false property bool isFullScreen: false // Use this variable to test if we are in fullscreen. Do not test _fullscreen : we need to clean memory before having the window (see .js file) + property bool layoutChanging: false property var _fullscreen: null on_FullscreenChanged: if( !_fullscreen) isFullScreen = false @@ -40,7 +41,7 @@ Rectangle { : conferenceLayout.item ? conferenceLayout.item.participantCount : 2 // States - property bool isAudioOnly: callModel && callModel.isConference && conferenceLayout.sourceComponent == gridComponent && !callModel.videoEnabled + property bool isAudioOnly: callModel && callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutAudioOnly property bool isReady : mainItem.callModel && (!mainItem.callModel.isConference || (mainItem.conferenceModel && mainItem.conferenceModel.isReady) @@ -255,7 +256,6 @@ Rectangle { backgroundRadius: width/2 colorSet: IncallStyle.buttons.record property CallModel callModel: mainItem.callModel - onCallModelChanged: if(!callModel) callModel.stopRecording() visible: SettingsModel.callRecorderEnabled && callModel && (callModel.recording || mainItem.isReady) toggled: callModel.recording @@ -273,7 +273,7 @@ Rectangle { isCustom: true backgroundRadius: width/2 colorSet: IncallStyle.buttons.screenshot - visible: SettingsModel.incallScreenshotEnabled && mainItem.isReady && mainItem.callModel && mainItem.callModel.snapshotEnabled + visible: SettingsModel.incallScreenshotEnabled && mainItem.isReady && mainItem.callModel && (!mainItem.callModel.isConference || mainItem.callModel.snapshotEnabled) onClicked: mainItem.callModel.takeSnapshot() //: 'Take Snapshot' : Tooltip for takking snapshot. tooltipText: qsTr('incallSnapshotTooltip') @@ -309,7 +309,7 @@ Rectangle { Layout.leftMargin: 70 Layout.rightMargin: rightMenu.visible ? 15 : 70 callModel: mainItem.callModel - cameraEnabled: !mainItem.isFullScreen + cameraEnabled: !mainItem.isFullScreen && !mainItem.layoutChanging } } Component{ @@ -319,7 +319,7 @@ Rectangle { callModel: mainItem.callModel isRightReducedLayout: rightMenu.visible isLeftReducedLayout: mainItem.listCallsOpened - cameraEnabled: !mainItem.isFullScreen + cameraEnabled: !mainItem.isFullScreen && !mainItem.layoutChanging } } RowLayout{ @@ -329,12 +329,50 @@ Rectangle { Layout.fillWidth: true Loader{ id: conferenceLayout - anchors.fill: parent - sourceComponent: mainItem.conferenceModel - ? mainItem.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker - ? activeSpeakerComponent - : gridComponent - : activeSpeakerComponent + anchors.fill: parent + + Timer{// Avoid Qt crashes when layout changes while videos are on + id: layoutDelay + interval: 100 + property int step : 0 + property var layoutMode + onTriggered: { + switch(step){ + case 2 : step = 0; mainItem.layoutChanging = false; break; + case 1: ++step; conferenceLayout.sourceComponent = conferenceLayout.getLayout(); layoutDelay.restart(); break; + case 0: if( mainItem.callModel.conferenceVideoLayout != layoutMode) + mainItem.callModel.conferenceVideoLayout = layoutMode + else { + ++step; + layoutDelay.restart() + } + break; + } + } + function begin(layoutMode){ + step = 0 + layoutDelay.layoutMode = layoutMode + mainItem.layoutChanging = true + layoutDelay.restart() + } + } + function getLayout(){ + return mainItem.conferenceModel + ? mainItem.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker + ? activeSpeakerComponent + : gridComponent + : activeSpeakerComponent + } + + Connections{ + target: mainItem.callModel + + onConferenceVideoLayoutChanged: { + layoutDelay.layoutMode = mainItem.callModel.conferenceVideoLayout + layoutDelay.restart() + } + } + sourceComponent: getLayout() active: mainItem.callModel && !mainItem.isFullScreen } Rectangle{ @@ -343,12 +381,16 @@ Rectangle { visible: !mainItem.isReady ColumnLayout { anchors.fill: parent - BusyIndicator{ + Loader{ Layout.preferredHeight: 40 Layout.preferredWidth: 40 Layout.alignment: Qt.AlignCenter - running: parent.visible - color: IncallStyle.buzyColor + active: parent.visible + sourceComponent: Component{ + BusyIndicator{ + color: IncallStyle.buzyColor + } + } } Text{ Layout.alignment: Qt.AlignCenter @@ -371,8 +413,11 @@ Rectangle { callModel: mainItem.callModel conferenceModel: mainItem.conferenceModel visible: false + enabled: !mainItem.layoutChanging onClose: rightMenu.visible = !rightMenu.visible - onLayoutChanging: conferenceLayout.item.clearAll(layoutMode) + onLayoutChanging: { + layoutDelay.begin(layoutMode) + } } } } @@ -488,15 +533,28 @@ Rectangle { isCustom: true backgroundRadius: 90 colorSet: callModel && callModel.cameraEnabled ? IncallStyle.buttons.cameraOn : IncallStyle.buttons.cameraOff - updating: callModel.videoEnabled && callModel.updating - enabled: !mainItem.isAudioOnly - onClicked: if(callModel){ + updating: callModel.videoEnabled && callModel.updating && !mainItem.layoutChanging + enabled: callModel && !callModel.pausedByUser + property bool _activateCamera: false + onClicked: if(callModel && !mainItem.layoutChanging){ if( callModel.isConference){// Only deactivate camera in conference. - callModel.cameraEnabled = !callModel.cameraEnabled + if(mainItem.isAudioOnly) { + var layout = SettingsModel.videoConferenceLayout != LinphoneEnums.ConferenceLayoutAudioOnly ? SettingsModel.videoConferenceLayout : LinphoneEnums.ConferenceLayoutGrid + layoutDelay.begin(layout) + camera._activateCamera = true + }else + callModel.cameraEnabled = !callModel.cameraEnabled }else{// In one-one, we deactivate all videos. callModel.videoEnabled = !callModel.videoEnabled } } + Connections{// Enable camera only when status is ok + target: callModel + onStatusChanged: if( camera._activateCamera && (status == LinphoneEnums.CallStatusConnected || status == LinphoneEnums.CallStatusIdle)){ + camera._activateCamera = false + callModel.cameraEnabled = true + } + } } } diff --git a/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml b/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml index 8d6626699..3a0e10a68 100644 --- a/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml +++ b/linphone-app/ui/views/App/Calls/IncallActiveSpeaker.qml @@ -29,45 +29,26 @@ Item { property int participantCount: callModel.isConference ? allDevices.count + 1 : 2 // +me. allDevices==0 if !conference - onParticipantCountChanged: {Qt.callLater(allDevices.updateCurrentDevice)} - property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel { id: allDevices callModel: mainItem.callModel - showMe: false - - onParticipantSpeaking: updateCurrentDevice() - + showMe: false onConferenceCreated: cameraView.resetCamera() - function updateCurrentDevice(){ - if( callModel ){ - if( callModel.isConference) { - var device = getLastActiveSpeaking() - if(device) // Get - cameraView.currentDevice = device - } - } - } - onMeChanged: if(cameraView.isPreview) { - cameraView.currentDevice = me - cameraView.resetCamera() - } } - function clearAll(layoutMode){ - if( layoutMode != LinphoneEnums.ConferenceLayoutActiveSpeaker){ - mainItem.cameraEnabled = false - miniViews.model = [] - } - } Sticker{ id: cameraView anchors.fill: parent anchors.leftMargin: isRightReducedLayout || isLeftReducedLayout? 30 : 140 anchors.rightMargin: isRightReducedLayout ? 10 : 140 callModel: mainItem.callModel - deactivateCamera: isPreview && callModel.pausedByUser + currentDevice: isPreview + ? allDevices.me + : callModel.isConference + ? allDevices.activeSpeaker + : null + deactivateCamera: !mainItem.cameraEnabled || (isPreview && callModel.pausedByUser) ? true : callModel.isConference ? (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused) ) @@ -76,17 +57,8 @@ Item { || !mainItem.isConferenceReady : (callModel && (callModel.pausedByUser || callModel.status === CallModel.CallStatusPaused || !callModel.videoEnabled) ) || currentDevice && !currentDevice.videoEnabled - - isVideoEnabled: !deactivateCamera isPreview: !preview.visible && mainItem.participantCount == 1 - onIsPreviewChanged: { - if( isPreview){ - currentDevice = allDevices.me - cameraView.resetCamera() - }else - allDevices.updateCurrentDevice() - cameraView.resetCamera() - } + onIsPreviewChanged: {cameraView.resetCamera() } isCameraFromDevice: isPreview isPaused: isPreview && callModel.pausedByUser ? false @@ -121,7 +93,7 @@ Item { sourceComponent: Sticker{ id: previewSticker - deactivateCamera: !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled + deactivateCamera: !mainItem.cameraEnabled || !mainItem.callModel || callModel.pausedByUser || !mainItem.callModel.cameraEnabled currentDevice: allDevices.me isPreview: true callModel: mainItem.callModel @@ -193,3 +165,4 @@ Item { } } + diff --git a/linphone-app/ui/views/App/Calls/IncallFullscreen.qml b/linphone-app/ui/views/App/Calls/IncallFullscreen.qml index 36d40b19f..27d63c4ce 100644 --- a/linphone-app/ui/views/App/Calls/IncallFullscreen.qml +++ b/linphone-app/ui/views/App/Calls/IncallFullscreen.qml @@ -68,6 +68,9 @@ Window { property ConferenceModel conferenceModel: callModel && callModel.conferenceModel property var _fullscreen: null property bool listCallsOpened: false + property bool layoutChanging: false + + property bool isAudioOnly: callModel && callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutAudioOnly signal openListCallsRequest() // --------------------------------------------------------------------------- @@ -221,7 +224,6 @@ Window { backgroundRadius: width/2 colorSet: IncallStyle.buttons.record property CallModel callModel: conference.callModel - onCallModelChanged: if(callModel) callModel.stopRecording() visible: SettingsModel.callRecorderEnabled && callModel toggled: callModel && callModel.recording @@ -239,7 +241,7 @@ Window { isCustom: true backgroundRadius: width/2 colorSet: IncallStyle.buttons.screenshot - visible: SettingsModel.incallScreenshotEnabled && conference.callModel && conference.callModel.snapshotEnabled + visible: SettingsModel.incallScreenshotEnabled && conference.callModel && (!conference.callModel.isConference || window.callModel.snapshotEnabled) onClicked: conference.callModel && conference.callModel.takeSnapshot() //: 'Take Snapshot' : Tooltip for takking snapshot. tooltipText: qsTr('incallSnapshotTooltip') @@ -280,6 +282,7 @@ Window { Layout.leftMargin: window.hideButtons ? 15 : 70 Layout.rightMargin: rightMenu.visible ? 15 : 70 callModel: conference.callModel + cameraEnabled: !conference.layoutChanging } } Component{ @@ -287,6 +290,7 @@ Window { IncallActiveSpeaker{ id: activeSpeaker callModel: conference.callModel + cameraEnabled: !conference.layoutChanging isRightReducedLayout: rightMenu.visible isLeftReducedLayout: conference.listCallsOpened } @@ -298,13 +302,48 @@ Window { Layout.fillHeight: true Layout.fillWidth: true - sourceComponent: conference.callModel - ? conference.conferenceModel - ? conference.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker - ? activeSpeakerComponent - : gridComponent - : activeSpeakerComponent - : null + Timer{// Avoid Qt crashes when layout changes while videos are on + id: layoutDelay + interval: 100 + property int step : 0 + property var layoutMode + onTriggered: { + switch(step){ + case 2 : step = 0; conference.layoutChanging = false; break; + case 1: ++step; conferenceLayout.sourceComponent = conferenceLayout.getLayout(); layoutDelay.restart(); break; + case 0: if( conference.callModel.conferenceVideoLayout != layoutMode) + conference.callModel.conferenceVideoLayout = layoutMode + else { + ++step; + layoutDelay.restart() + } + break; + } + } + function begin(layoutMode){ + step = 0 + layoutDelay.layoutMode = layoutMode + conference.layoutChanging = true + layoutDelay.restart() + } + } + function getLayout(){ + return conference.conferenceModel + ? conference.callModel.conferenceVideoLayout == LinphoneEnums.ConferenceLayoutActiveSpeaker + ? activeSpeakerComponent + : gridComponent + : activeSpeakerComponent + } + + Connections{ + target: conference.callModel + + onConferenceVideoLayoutChanged: { + layoutDelay.layoutMode = conference.callModel.conferenceVideoLayout + layoutDelay.restart() + } + } + sourceComponent: getLayout() active: conference.callModel ColumnLayout { anchors.fill: parent @@ -333,6 +372,9 @@ Window { conferenceModel: conference.conferenceModel visible: false onClose: rightMenu.visible = !rightMenu.visible + onLayoutChanging: { + layoutDelay.begin(layoutMode) + } } } } @@ -451,8 +493,15 @@ Window { backgroundRadius: 90 colorSet: callModel && callModel.cameraEnabled ? IncallStyle.buttons.cameraOn : IncallStyle.buttons.cameraOff updating: callModel && callModel.videoEnabled && callModel.updating - enabled: callModel && callModel.videoEnabled - onClicked: if(callModel) callModel.cameraEnabled = !callModel.cameraEnabled + enabled: callModel && !callModel.pausedByUser + onClicked: if(callModel && !conference.layoutChanging){ + if( callModel.isConference){// Only deactivate camera in conference. + callModel.cameraEnabled = !callModel.cameraEnabled + }else{// In one-one, we deactivate all videos. + if(callModel.videoEnabled ) Qt.callLater(function(){window.exit()}) + callModel.videoEnabled = !callModel.videoEnabled + } + } } } RowLayout{ diff --git a/linphone-app/ui/views/App/Calls/IncallGrid.qml b/linphone-app/ui/views/App/Calls/IncallGrid.qml index 1369bf7ec..56685c608 100644 --- a/linphone-app/ui/views/App/Calls/IncallGrid.qml +++ b/linphone-app/ui/views/App/Calls/IncallGrid.qml @@ -27,12 +27,6 @@ Mosaic { // On grid view, we limit the quality if there are enough participants// The vga mode has been activated from the factory rc //onParticipantCountChanged: participantCount > ConstantsCpp.maxMosaicParticipants ? SettingsModel.setLimitedMosaicQuality() : SettingsModel.setHighMosaicQuality() - function clearAll(layoutMode){ - if( layoutMode != 2 && layoutMode != LinphoneEnums.ConferenceLayoutGrid){ - clear() - gridModel.model = [] - } - } delegateModel: DelegateModel{ id: gridModel property ParticipantDeviceProxyModel participantDevices : ParticipantDeviceProxyModel { diff --git a/linphone-app/ui/views/App/Calls/WaitingRoom.qml b/linphone-app/ui/views/App/Calls/WaitingRoom.qml index f63a9142c..eafe22ec0 100644 --- a/linphone-app/ui/views/App/Calls/WaitingRoom.qml +++ b/linphone-app/ui/views/App/Calls/WaitingRoom.qml @@ -131,10 +131,13 @@ Rectangle { RowLayout{ Layout.fillWidth: true Layout.fillHeight: true + spacing: 0 Item{ id: stickerView Layout.fillHeight: true Layout.fillWidth: true + Layout.leftMargin: 10 + Layout.rightMargin: 10 Sticker{ id: contentsStack @@ -175,7 +178,7 @@ Rectangle { Loader{ id: multimediaLoader Layout.fillHeight: true - Layout.leftMargin: 10 + Layout.leftMargin: 0 Layout.rightMargin: 10 Layout.minimumHeight: item? item.fitHeight : 0 Layout.minimumWidth: item? item.fitWidth : 0 diff --git a/linphone-app/ui/views/App/Dialog/NewConference.qml b/linphone-app/ui/views/App/Dialog/NewConference.qml index d58f8ea5b..94c3c652b 100644 --- a/linphone-app/ui/views/App/Dialog/NewConference.qml +++ b/linphone-app/ui/views/App/Dialog/NewConference.qml @@ -299,9 +299,9 @@ DialogPlus { } function setDate(date){ currentDate = date - text = date.toLocaleDateString(scheduleForm.locale, Qt.ISODate) + text = Utils.exactDate(date).toLocaleDateString(scheduleForm.locale, Qt.ISODate) } - text: conferenceManager.conferenceInfoModel ? conferenceManager.conferenceInfoModel.dateTime.toLocaleDateString(scheduleForm.locale, Qt.ISODate) : '' + text: conferenceManager.conferenceInfoModel ? Utils.exactDate(conferenceManager.conferenceInfoModel.dateTime).toLocaleDateString(scheduleForm.locale, Qt.ISODate) : '' icon: 'drop_down_custom' MouseArea{ anchors.fill: parent @@ -319,12 +319,15 @@ DialogPlus { TextField{id: timeField; Layout.preferredWidth: parent.cellWidth color: NewConferenceStyle.fields.textColor; font.weight: NewConferenceStyle.fields.weight; font.pointSize: NewConferenceStyle.fields.pointSize function getTime(){ - return Date.fromLocaleTimeString(scheduleForm.locale, timeField.text, 'hh:mm') + return Date.fromLocaleTimeString(scheduleForm.locale, timeField._text, 'hh:mm') } function setTime(date){ - text = date.toLocaleTimeString(scheduleForm.locale, 'hh:mm') + _text = date.toLocaleTimeString(scheduleForm.locale, 'hh:mm') + text = UtilsCpp.toTimeString(date, 'hh:mm')// Display the unchanged time } - text: conferenceManager.conferenceInfoModel? conferenceManager.conferenceInfoModel.dateTime.toLocaleTimeString(scheduleForm.locale, 'hh:mm') : '' + // hidden time to be used from JS : JS Local time can be wrong on Windows because of daylights that are not takken account. + property string _text: conferenceManager.conferenceInfoModel? conferenceManager.conferenceInfoModel.dateTime.toLocaleTimeString(scheduleForm.locale, 'hh:mm') : '' + text: conferenceManager.conferenceInfoModel? UtilsCpp.toTimeString(conferenceManager.conferenceInfoModel.dateTimeUtc, 'hh:mm') : '' icon: 'drop_down_custom' onEditingFinished: if(rightStackView.currentItemType === 2) { @@ -554,7 +557,6 @@ DialogPlus { function removeParticipant(entry){ smartSearchBar.removeAddressToIgnore(entry.sipAddress) selectedParticipants.removeModel(entry) - ++lastContacts.reloadCount } @@ -576,7 +578,7 @@ DialogPlus { id:selectedParticipants chatRoomModel:null } - onEntryClicked: actions[0].handler(entry) + onEntryClicked: participantView.showSubtitle = !participantView.showSubtitle } } } diff --git a/linphone-app/ui/views/App/Main/Assistant/AssistantHome.qml b/linphone-app/ui/views/App/Main/Assistant/AssistantHome.qml index 759e3be1c..1352c6693 100644 --- a/linphone-app/ui/views/App/Main/Assistant/AssistantHome.qml +++ b/linphone-app/ui/views/App/Main/Assistant/AssistantHome.qml @@ -138,12 +138,14 @@ ColumnLayout { append({ $text: qsTr('useOtherSipAccount'), $view: 'UseOtherSipAccount', - $viewType: 'UseOtherSipAccount' + $viewType: 'UseOtherSipAccount', + $props: {} }) append( { $text: qsTr('fetchRemoteConfiguration'), $view: 'FetchRemoteConfiguration', - $viewType: 'FetchRemoteConfiguration' + $viewType: 'FetchRemoteConfiguration', + $props: {} }) } } diff --git a/linphone-app/ui/views/App/Main/Assistant/UseAppSipAccount.qml b/linphone-app/ui/views/App/Main/Assistant/UseAppSipAccount.qml index e7bbd5c14..4870c8155 100644 --- a/linphone-app/ui/views/App/Main/Assistant/UseAppSipAccount.qml +++ b/linphone-app/ui/views/App/Main/Assistant/UseAppSipAccount.qml @@ -89,7 +89,7 @@ AssistantAbstractView { function setCountryCode (index) { var model = telephoneNumbersModel - assistantModel.countryCode = index !== -1 ? model.data(model.index(index, 0)).countryCode || '' : '' + assistantModel.countryCode = index !== -1 ? model.data(model.index(index, 0),"countryCode") || '' : '' } configFilename: 'use-app-sip-account.rc' diff --git a/linphone-app/ui/views/App/Main/ContactEdit.js b/linphone-app/ui/views/App/Main/ContactEdit.js index 11da59f9c..0c3b8898a 100644 --- a/linphone-app/ui/views/App/Main/ContactEdit.js +++ b/linphone-app/ui/views/App/Main/ContactEdit.js @@ -79,7 +79,7 @@ function removeContact () { if (status) { window.unlockView() window.setView('Contacts') - Linphone.ContactsListModel.remove(_contact) + Linphone.ContactsListModel.removeContact(_contact) } }) } diff --git a/linphone-app/ui/views/App/Main/ContactEdit.qml b/linphone-app/ui/views/App/Main/ContactEdit.qml index b849d7016..d150abf6b 100644 --- a/linphone-app/ui/views/App/Main/ContactEdit.qml +++ b/linphone-app/ui/views/App/Main/ContactEdit.qml @@ -35,9 +35,7 @@ ColumnLayout { Component.onDestruction: {_vcard=null}// Need to set it to null because of not calling destructor if not. Component.onCompleted:{ var sipAddress = contactEdit.sipAddress - var contact = contactEdit._contact = SipAddressesModel.mapSipAddressToContact( - sipAddress - ) + var contact = contactEdit._contact = ContactsListModel.getContactModelFromAddress(sipAddress) if (!contact) { // Add a new contact. @@ -241,7 +239,7 @@ ColumnLayout { sipAddresses: _contact ? _contact.vcard.sipAddresses : [ contactEdit.sipAddress ] function viewConversation(chatRoomModel){ - if( chatRoomModel){ + if( chatRoomModel && !chatRoomModel.updating){ window.setView('Conversation', { chatRoomModel:chatRoomModel }, function(){ diff --git a/linphone-app/ui/views/App/Main/Conversation.qml b/linphone-app/ui/views/App/Main/Conversation.qml index b2cf8b343..40fa59185 100644 --- a/linphone-app/ui/views/App/Main/Conversation.qml +++ b/linphone-app/ui/views/App/Main/Conversation.qml @@ -103,20 +103,9 @@ ColumnLayout { //username: Logic.getUsername() username: chatRoomModel?chatRoomModel.username:( conversation._sipAddressObserver ? UtilsCpp.getDisplayName(conversation._sipAddressObserver.peerAddress) : '') - visible: !groupChat.visible + isOneToOne: chatRoomModel==undefined || chatRoomModel.isOneToOne==undefined || chatRoomModel.isOneToOne } - Icon { - id: groupChat - - Layout.preferredHeight: ConversationStyle.bar.groupChatSize - Layout.preferredWidth: ConversationStyle.bar.groupChatSize - - icon: ConversationStyle.bar.groupChatIcon - overwriteColor: ConversationStyle.bar.groupChatColor - iconSize: ConversationStyle.bar.groupChatSize - visible: chatRoomModel && !chatRoomModel.isOneToOne - } Item{ Layout.fillHeight: true Layout.fillWidth: true @@ -127,7 +116,7 @@ ColumnLayout { ColumnLayout{ property int maximumContentWidth: contactBar.width - -(avatar.visible?avatar.width:0)-(groupChat.visible?groupChat.width:0) + -(avatar.visible?avatar.width:0) -actionBar.width - (secureIcon.visible?secureIcon.width :0) -3*ConversationStyle.bar.spacing Layout.fillHeight: true diff --git a/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml index cdbd607d2..1097334e2 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/InfoChatRoom.qml @@ -88,7 +88,7 @@ DialogPlus { secure: chatRoomModel.haveEncryption, visible: true, secureIconVisibleHandler : function(entry) { - return entry.sipAddress && chatRoomModel && chatRoomModel.haveEncryption && UtilsCpp.hasCapability(entry.sipAddress, LinphoneEnums.FriendCapabilityLimeX3Dh); + return entry && entry.sipAddress && chatRoomModel && chatRoomModel.haveEncryption && UtilsCpp.hasCapability(entry.sipAddress, LinphoneEnums.FriendCapabilityLimeX3Dh); }, handler: function (entry) { selectedParticipants.addAddress(entry.sipAddress) diff --git a/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml b/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml index 9e339de9c..c3c0c44fe 100644 --- a/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml +++ b/linphone-app/ui/views/App/Main/Dialogs/NewChatRoom.qml @@ -90,7 +90,7 @@ DialogPlus { Layout.fillWidth: true Layout.topMargin:15 spacing:4 - visible: SettingsModel.secureChatEnabled + visible: SettingsModel.secureChatEnabled && SettingsModel.standardChatEnabled Text { Layout.fillWidth: true //: 'Would you like to encrypt your chat?' : Ask about setting the chat room as secured. @@ -157,13 +157,24 @@ DialogPlus { ColumnLayout { Layout.fillWidth: true spacing:10 - Text{ - textFormat: Text.RichText - //: 'Subject' : Label of a text field about the subject of the chat room - text :qsTr('subjectLabel') +'*' - color: NewChatRoomStyle.subjectTitleColor - font.pointSize: Units.dp * 11 - font.weight: Font.DemiBold + RowLayout{ + Icon{ + id:defaultSecure + Layout.alignment: Qt.AlignCenter + Layout.preferredHeight: visible? 20 : 0 + Layout.preferredWidth: visible? 20 : 0 + icon: 'secure_on' + iconSize:20 + visible: SettingsModel.secureChatEnabled && !SettingsModel.standardChatEnabled + } + Text{ + textFormat: Text.RichText + //: 'Subject' : Label of a text field about the subject of the chat room + text :qsTr('subjectLabel') +'*' + color: NewChatRoomStyle.subjectTitleColor + font.pointSize: Units.dp * 11 + font.weight: Font.DemiBold + } } TextField { id:subject diff --git a/linphone-app/ui/views/App/Main/HistoryView.qml b/linphone-app/ui/views/App/Main/HistoryView.qml index 83eb23794..5361edba4 100644 --- a/linphone-app/ui/views/App/Main/HistoryView.qml +++ b/linphone-app/ui/views/App/Main/HistoryView.qml @@ -14,12 +14,14 @@ import 'HistoryView.js' as Logic ColumnLayout { id: historyView - property string peerAddress - property string fullPeerAddress + property var entry + property string peerAddress : entry ? entry.sipAddress : '' + property string fullPeerAddress : entry ? entry.sipAddress : '' + property var _sipAddressObserver: peerAddress?SipAddressesModel.getSipAddressObserver((fullPeerAddress?fullPeerAddress:peerAddress), ''):null - + onEntryChanged: historyProxyModel.resetMessageCount() // --------------------------------------------------------------------------- spacing: 0 @@ -56,15 +58,21 @@ ColumnLayout { historyView._sipAddressObserver.presenceStatus ):null - username: peerAddress && historyView._sipAddressObserver? UtilsCpp.getDisplayName(historyView._sipAddressObserver.peerAddress):null - visible:peerAddress + username: historyView.entry && historyView.entry.wasConference + ? historyView.entry.title + : peerAddress && historyView._sipAddressObserver + ? UtilsCpp.getDisplayName(historyView._sipAddressObserver.peerAddress) + : null + visible: peerAddress } ContactDescription { Layout.fillHeight: true Layout.fillWidth: true - subtitleText: SipAddressesModel.cleanSipAddress(sipAddress)(historyView.peerAddress) + subtitleText: historyView.entry && historyView.entry.wasConference + ? '' + : SipAddressesModel.cleanSipAddress(historyView.peerAddress) subtitleColor: HistoryViewStyle.bar.description.subtitleColor titleText: avatar.username titleColor: HistoryViewStyle.bar.description.titleColor @@ -75,10 +83,10 @@ ColumnLayout { Layout.fillHeight: true spacing: HistoryViewStyle.bar.actions.spacing - ActionBar { anchors.verticalCenter: parent.verticalCenter iconSize: HistoryViewStyle.bar.actions.call.iconSize + visible: historyView.entry ? !historyView.entry.wasConference : false ActionButton { isCustom: true @@ -129,7 +137,7 @@ ColumnLayout { backgroundRadius: 4 colorSet: historyView._sipAddressObserver && historyView._sipAddressObserver.contact ? ConversationStyle.bar.actions.edit.viewContact : ConversationStyle.bar.actions.edit.addContact iconSize: HistoryViewStyle.bar.actions.edit.iconSize - visible: peerAddress && SettingsModel.contactsEnabled + visible: SettingsModel.contactsEnabled && historyView.entry ? !historyView.entry.wasConference : false onClicked: window.setView('ContactEdit', { sipAddress: historyView.peerAddress }) tooltipText: peerAddress?Logic.getEditTooltipText():'' @@ -158,9 +166,7 @@ ColumnLayout { Layout.fillWidth: true onEntryClicked:{ - historyView.fullPeerAddress=entry.sipAddress - historyView.peerAddress=entry.sipAddress - historyProxyModel.resetMessageCount() + historyView.entry = entry } proxyModel: HistoryProxyModel { diff --git a/linphone-app/ui/views/App/Main/MainWindow.qml b/linphone-app/ui/views/App/Main/MainWindow.qml index e84ded5f2..b60cc6661 100644 --- a/linphone-app/ui/views/App/Main/MainWindow.qml +++ b/linphone-app/ui/views/App/Main/MainWindow.qml @@ -307,10 +307,12 @@ ApplicationWindow { visible: SettingsModel.contactsEnabled onSelected: { + ContactsListModel.update() timeline.model.unselectAll() setView('Contacts') } onClicked:{ + ContactsListModel.update() setView('Contacts') } Icon{ diff --git a/linphone-app/ui/views/App/Settings/SettingsVideo.qml b/linphone-app/ui/views/App/Settings/SettingsVideo.qml index bf28782fa..ed3c22b49 100644 --- a/linphone-app/ui/views/App/Settings/SettingsVideo.qml +++ b/linphone-app/ui/views/App/Settings/SettingsVideo.qml @@ -69,9 +69,10 @@ TabContainer { currentIndex: { var preset = SettingsModel.videoPreset - return Number(Utils.findIndex(model, function (value) { + var index = Number(Utils.findIndex(model, function (value) { return preset === value.value })) + return index>=0 ? index : 0; } model: [{ diff --git a/linphone-app/ui/views/App/Styles/Calls/CallsWindowStyle.qml b/linphone-app/ui/views/App/Styles/Calls/CallsWindowStyle.qml index f0bd8069e..d02d535fa 100644 --- a/linphone-app/ui/views/App/Styles/Calls/CallsWindowStyle.qml +++ b/linphone-app/ui/views/App/Styles/Calls/CallsWindowStyle.qml @@ -37,16 +37,18 @@ QtObject { property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_fg').color property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_fg').color } - property QtObject newConference: QtObject { + property QtObject mergeConference: QtObject { property int iconSize: 40 - property string name : 'newConference' - property string icon : 'conference_custom' + property string name : 'mergeConference' + property string icon : 'conference_merge_custom' property color backgroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_n', icon, 'ma_n_b_bg').color property color backgroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_h', icon, 'ma_h_b_bg').color property color backgroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_p', icon, 'ma_p_b_bg').color + property color backgroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_bg_d', icon, 'ma_d_b_bg').color property color foregroundNormalColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_n', icon, 'ma_n_b_fg').color property color foregroundHoveredColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_h', icon, 'ma_h_b_fg').color property color foregroundPressedColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_p', icon, 'ma_p_b_fg').color + property color foregroundDisabledColor : ColorsList.addImageColor(sectionName+'_'+name+'_fg_d', icon, 'ma_d_b_fg').color } property QtObject closeButton: QtObject{ diff --git a/linphone-sdk b/linphone-sdk index a286c00b6..3a5f2c321 160000 --- a/linphone-sdk +++ b/linphone-sdk @@ -1 +1 @@ -Subproject commit a286c00b618e218fde8314498bb49101c8fd9214 +Subproject commit 3a5f2c32145cc925dbc067f17d7d25c45fef1518