From eb5b3b514188e6b1df4ccc641678b3855451c06d Mon Sep 17 00:00:00 2001 From: Gaelle Braud Date: Tue, 23 Apr 2024 09:23:00 +0200 Subject: [PATCH] FIXES : conf creation loading+error; fix info popup layout move contact edition in contact page (switch to contact tab if creation requested) fix contact creation + select new contact on creation conference info list : creation signal (to finish when conference scheduler is updated, see comment) fix crash if no vcard fix calendar ui +spacings layout polish (! on string in meetinglist) --- Linphone/core/App.hpp | 2 +- Linphone/core/call/CallCore.cpp | 10 +- .../core/conference/ConferenceInfoCore.cpp | 31 +- .../core/conference/ConferenceInfoList.cpp | 55 ++- .../core/conference/ConferenceInfoList.hpp | 7 +- .../core/conference/ConferenceInfoProxy.cpp | 8 +- .../core/conference/ConferenceInfoProxy.hpp | 15 +- Linphone/core/friend/FriendCore.cpp | 36 +- Linphone/core/search/MagicSearchList.cpp | 30 +- Linphone/core/search/MagicSearchList.hpp | 3 + Linphone/core/search/MagicSearchProxy.cpp | 22 +- Linphone/core/search/MagicSearchProxy.hpp | 2 + .../data/image/video-conference-selected.svg | 32 ++ Linphone/data/image/video-conference.svg | 1 + Linphone/model/call/CallModel.cpp | 1 - .../model/conference/ConferenceInfoModel.cpp | 10 +- .../model/conference/ConferenceInfoModel.hpp | 2 + Linphone/model/conference/ConferenceModel.cpp | 2 - .../conference/ConferenceSchedulerModel.cpp | 5 + .../conference/ConferenceSchedulerModel.hpp | 2 + Linphone/model/core/CoreModel.hpp | 5 +- Linphone/model/friend/FriendModel.cpp | 66 ++- Linphone/model/search/MagicSearchModel.cpp | 5 - Linphone/tool/LinphoneEnums.hpp | 4 +- Linphone/tool/Utils.cpp | 16 + Linphone/tool/Utils.hpp | 3 + Linphone/view/App/CallsWindow.qml | 13 +- Linphone/view/App/Layout/MainLayout.qml | 17 +- Linphone/view/App/Main.qml | 28 +- Linphone/view/CMakeLists.txt | 2 + Linphone/view/Item/Calendar.qml | 125 ++--- Linphone/view/Item/CalendarComboBox.qml | 6 + Linphone/view/Item/CheckableButton.qml | 2 +- Linphone/view/Item/Contact/ContactEdition.qml | 462 +++++++++--------- Linphone/view/Item/Contact/ContactsList.qml | 13 +- Linphone/view/Item/InformationPopup.qml | 5 +- Linphone/view/Item/LoadingPopup.qml | 32 ++ Linphone/view/Item/Meeting/MeetingList.qml | 47 +- Linphone/view/Item/Meeting/MeetingSetUp.qml | 2 - Linphone/view/Item/VerticalTabBar.qml | 4 +- Linphone/view/Layout/Call/CallLayout.qml | 12 +- .../view/Layout/Contact/ContactLayout.qml | 1 - Linphone/view/Layout/RightPanelLayout.qml | 30 ++ Linphone/view/Page/Main/AbstractMainPage.qml | 36 +- Linphone/view/Page/Main/CallPage.qml | 3 +- Linphone/view/Page/Main/ContactPage.qml | 45 +- Linphone/view/Page/Main/MeetingPage.qml | 57 ++- Linphone/view/Style/AppIcons.qml | 2 + 48 files changed, 809 insertions(+), 510 deletions(-) create mode 100644 Linphone/data/image/video-conference-selected.svg create mode 100644 Linphone/data/image/video-conference.svg create mode 100644 Linphone/view/Item/LoadingPopup.qml create mode 100644 Linphone/view/Layout/RightPanelLayout.qml diff --git a/Linphone/core/App.hpp b/Linphone/core/App.hpp index 2fcd36540..204eb6652 100644 --- a/Linphone/core/App.hpp +++ b/Linphone/core/App.hpp @@ -102,7 +102,7 @@ public: void onLoggerInitialized(); - QQuickWindow *getCallsWindow(QVariant callGui); + QQuickWindow *getCallsWindow(QVariant callGui = QVariant()); void setCallsWindowProperty(const char *id, QVariant property); void closeCallsWindow(); diff --git a/Linphone/core/call/CallCore.cpp b/Linphone/core/call/CallCore.cpp index a511c0c50..68594b758 100644 --- a/Linphone/core/call/CallCore.cpp +++ b/Linphone/core/call/CallCore.cpp @@ -136,7 +136,15 @@ void CallCore::setSelf(QSharedPointer me) { mCallModelConnection->invokeToModel([this]() { mCallModel->stopRecording(); }); }); mCallModelConnection->makeConnectToModel(&CallModel::recordingChanged, [this](bool recording) { - mCallModelConnection->invokeToCore([this, recording]() { setRecording(recording); }); + mCallModelConnection->invokeToCore([this, recording]() { + setRecording(recording); + if (recording == false) { + Utils::showInformationPopup(tr("Enregistrement terminé"), + tr("L'appel a été enregistré dans le fichier : %1") + .arg(QString::fromStdString(mCallModel->getRecordFile())), + true, App::getInstance()->getCallsWindow()); + } + }); }); mCallModelConnection->makeConnectToCore(&CallCore::lVerifyAuthenticationToken, [this](bool verified) { mCallModelConnection->invokeToModel( diff --git a/Linphone/core/conference/ConferenceInfoCore.cpp b/Linphone/core/conference/ConferenceInfoCore.cpp index fc426a5b0..c4ca68b3a 100644 --- a/Linphone/core/conference/ConferenceInfoCore.cpp +++ b/Linphone/core/conference/ConferenceInfoCore.cpp @@ -194,33 +194,37 @@ void ConferenceInfoCore::setSelf(QSharedPointer me) { &ConferenceInfoModel::schedulerStateChanged, [this](linphone::ConferenceScheduler::State state) { auto confInfoState = mConferenceInfoModel->getState(); QString uri; - if (state == linphone::ConferenceScheduler::State::Ready) + if (state == linphone::ConferenceScheduler::State::Ready) { uri = mConferenceInfoModel->getConferenceScheduler()->getUri(); + } mConfInfoModelConnection->invokeToCore([this, state = LinphoneEnums::fromLinphone(state), infoState = LinphoneEnums::fromLinphone(confInfoState), uri] { - lDebug() << "scheduler state changed" << state; setConferenceSchedulerState(state); setConferenceInfoState(infoState); if (state == LinphoneEnums::ConferenceSchedulerState::Ready) { setUri(uri); + mConfInfoModelConnection->invokeToModel([this, uri] { + CoreModel::getInstance()->conferenceInfoCreated( + mConferenceInfoModel->getConferenceInfo()); + }); } + setConferenceSchedulerState(state); + }); + }); + mConfInfoModelConnection->makeConnectToModel( + &ConferenceInfoModel::infoStateChanged, [this](linphone::ConferenceInfo::State state) { + auto uri = mConferenceInfoModel->getConferenceScheduler()->getUri(); + mConfInfoModelConnection->invokeToCore([this, infoState = LinphoneEnums::fromLinphone(state), uri] { + setConferenceInfoState(infoState); }); }); mConfInfoModelConnection->makeConnectToModel( &ConferenceInfoModel::invitationsSent, - [this](const std::list> &failedInvitations) { - lDebug() << "invitations sent"; - }); + [this](const std::list> &failedInvitations) {}); } else { // Create mCoreModelConnection = QSharedPointer>( new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); - mCoreModelConnection->makeConnectToModel( - &CoreModel::conferenceInfoReceived, - [this](const std::shared_ptr &core, - const std::shared_ptr &conferenceInfo) { - lDebug() << "CONF INFO RECEIVED =================="; - }); } } } @@ -566,6 +570,11 @@ void ConferenceInfoCore::save() { }); } else { mCoreModelConnection->invokeToModel([this, thisCopy]() { + if (CoreModel::getInstance()->getCore()->getDefaultAccount()->getState() != + linphone::RegistrationState::Ok) { + Utils::showInformationPopup(tr("Erreur"), tr("Votre compte est déconnecté"), false); + return; + } auto linphoneConf = CoreModel::getInstance()->getCore()->findConferenceInformationFromUri(ToolModel::interpretUrl(mUri)); diff --git a/Linphone/core/conference/ConferenceInfoList.cpp b/Linphone/core/conference/ConferenceInfoList.cpp index 92998b7e6..155c7a584 100644 --- a/Linphone/core/conference/ConferenceInfoList.cpp +++ b/Linphone/core/conference/ConferenceInfoList.cpp @@ -68,7 +68,17 @@ void ConferenceInfoList::setSelf(QSharedPointer me) { int currentDateIndex = sort(*items); add(*items); updateHaveCurrentDate(); - setCurrentDateIndex(mHaveCurrentDate ? currentDateIndex + 1 : currentDateIndex); + if (mLastConfInfoInserted) { + int index = -1; + // TODO : uncomment when linphone conference scheduler updated + // and model returns the scheduler conferenceInfo uri + index = findConfInfoIndexByUri(mLastConfInfoInserted->getUri()); + // int index2; + // get(mLastConfInfoInserted.get(), &index2); + if (index != -1) setCurrentDateIndex(index); + else setCurrentDateIndex(mHaveCurrentDate ? currentDateIndex + 1 : currentDateIndex); + mLastConfInfoInserted = nullptr; + } else setCurrentDateIndex(mHaveCurrentDate ? currentDateIndex + 1 : currentDateIndex); delete items; }); }); @@ -76,10 +86,20 @@ void ConferenceInfoList::setSelf(QSharedPointer me) { mCoreModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, &ConferenceInfoList::lUpdate); mCoreModelConnection->makeConnectToModel(&CoreModel::conferenceInfoReceived, &ConferenceInfoList::lUpdate); - mCoreModelConnection->makeConnectToModel(&CoreModel::conferenceStateChanged, [this] { - lDebug() << "list: conf state changed"; - lUpdate(); - }); + mCoreModelConnection->makeConnectToModel( + &CoreModel::conferenceInfoCreated, [this](const std::shared_ptr &confInfo) { + auto confInfoCore = ConferenceInfoCore::create(confInfo); + auto haveConf = + std::find_if(mList.begin(), mList.end(), [confInfoCore](const QSharedPointer &item) { + auto isConfInfo = item.objectCast(); + if (!isConfInfo) return false; + return isConfInfo->getUri() == confInfoCore->getUri(); + }); + if (haveConf == mList.end()) { + mLastConfInfoInserted = confInfoCore; + emit lUpdate(); + } + }); mCoreModelConnection->makeConnectToModel( &CoreModel::callCreated, [this](const std::shared_ptr &call) { lDebug() << "call created" << Utils::coreStringToAppString(call->getRemoteAddress()->asString()); @@ -128,19 +148,13 @@ void ConferenceInfoList::setCurrentDateIndex(int index) { } } -QSharedPointer -ConferenceInfoList::get(std::shared_ptr conferenceInfo) const { - auto uri = Utils::coreStringToAppString(conferenceInfo->getUri()->asStringUriOnly()); - for (auto item : mList) { - auto model = item.objectCast(); - if (model) { - auto confUri = model->getUri(); - if (confUri == uri) { - return model; - } - } +int ConferenceInfoList::findConfInfoIndexByUri(const QString &uri) { + auto items = getSharedList(); + for (int i = 0; i < items.size(); ++i) { + if (!items[i]) continue; + if (items[i]->getUri() == uri) return i; } - return nullptr; + return -1; } QSharedPointer @@ -160,13 +174,6 @@ ConferenceInfoList::build(const std::shared_ptr &confe } else return nullptr; } -void ConferenceInfoList::remove(const int &row) { - // List is modified asynchronously - // so no need to specify the begin/endRemoveRows - auto item = mList[row].objectCast(); - if (item) emit item->lDeleteConferenceInfo(); -} - QHash ConferenceInfoList::roleNames() const { QHash roles; roles[Qt::DisplayRole] = "$modelData"; diff --git a/Linphone/core/conference/ConferenceInfoList.hpp b/Linphone/core/conference/ConferenceInfoList.hpp index ef10df25f..e696957c7 100644 --- a/Linphone/core/conference/ConferenceInfoList.hpp +++ b/Linphone/core/conference/ConferenceInfoList.hpp @@ -46,10 +46,9 @@ public: int getCurrentDateIndex() const; void setCurrentDateIndex(int index); - QSharedPointer get(std::shared_ptr conferenceInfo) const; - QSharedPointer build(const std::shared_ptr &conferenceInfo) const; + int findConfInfoIndexByUri(const QString &uri); - void remove(const int &row); + QSharedPointer build(const std::shared_ptr &conferenceInfo) const; QHash roleNames() const override; @@ -64,6 +63,8 @@ signals: private: QSharedPointer> mCoreModelConnection; + std::shared_ptr mCoreModel; + QSharedPointer mLastConfInfoInserted; bool mHaveCurrentDate = false; int mCurrentDateIndex = -1; DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/core/conference/ConferenceInfoProxy.cpp b/Linphone/core/conference/ConferenceInfoProxy.cpp index 50262353f..3353b6e92 100644 --- a/Linphone/core/conference/ConferenceInfoProxy.cpp +++ b/Linphone/core/conference/ConferenceInfoProxy.cpp @@ -32,7 +32,6 @@ ConferenceInfoProxy::ConferenceInfoProxy(QObject *parent) : SortFilterProxy(pare invalidate(); updateCurrentDateIndex(); }); - connect(this, &ConferenceInfoProxy::lUpdate, mList.get(), &ConferenceInfoList::lUpdate); connect(mList.get(), &ConferenceInfoList::haveCurrentDateChanged, [this] { invalidate(); updateCurrentDateIndex(); @@ -77,10 +76,9 @@ bool ConferenceInfoProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sou if (ciCore) { if (!ciCore->getSubject().contains(mSearchText)) return false; QDateTime currentDateTime = QDateTime::currentDateTimeUtc(); - // TODO : use enums - if (mFilterType == 0) { + if (mFilterType == int(ConferenceInfoProxy::ConferenceInfoFiltering::None)) { return true; - } else if (mFilterType == 1) { + } else if (mFilterType == int(ConferenceInfoProxy::ConferenceInfoFiltering::Future)) { auto res = ciCore->getEndDateTimeUtc() >= currentDateTime; return res; } else return mFilterType == -1; @@ -88,4 +86,4 @@ bool ConferenceInfoProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sou return !mList->haveCurrentDate(); } return false; -} +} \ No newline at end of file diff --git a/Linphone/core/conference/ConferenceInfoProxy.hpp b/Linphone/core/conference/ConferenceInfoProxy.hpp index 537b9ce88..df76d0ae4 100644 --- a/Linphone/core/conference/ConferenceInfoProxy.hpp +++ b/Linphone/core/conference/ConferenceInfoProxy.hpp @@ -33,30 +33,31 @@ class ConferenceInfoProxy : public SortFilterProxy, public AbstractObject { Q_PROPERTY(bool haveCurrentDate READ haveCurrentDate NOTIFY haveCurrentDateChanged) Q_PROPERTY(int currentDateIndex READ getCurrentDateIndex NOTIFY currentDateIndexChanged) +public: + enum ConferenceInfoFiltering { None = 0, Future = 1 }; + Q_ENUM(ConferenceInfoFiltering) public: ConferenceInfoProxy(QObject *parent = Q_NULLPTR); ~ConferenceInfoProxy(); QString getSearchText() const; void setSearchText(const QString &search); - + bool haveCurrentDate() const; - + int getCurrentDateIndex() const; void updateCurrentDateIndex(); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - // Do not use the sort feature. We cannot retrieve indexes with mapToSource because Qt return -1 for items that are not displayed. - // We need it to know where - // The workaround is to implement ourself the sort into the List. - //bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; + // Do not use the sort feature. We cannot retrieve indexes with mapToSource because Qt return -1 for items that are + // not displayed. We need it to know where The workaround is to implement ourself the sort into the List. + // bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; signals: void searchTextChanged(); void haveCurrentDateChanged(); void currentDateIndexChanged(); - void lUpdate(); private: QString mSearchText; diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp index a755c2388..198b43add 100644 --- a/Linphone/core/friend/FriendCore.cpp +++ b/Linphone/core/friend/FriendCore.cpp @@ -54,10 +54,12 @@ FriendCore::FriendCore(const std::shared_ptr &contact) : QObje mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); mPictureUri = Utils::coreStringToAppString(contact->getPhoto()); auto vcard = contact->getVcard(); - mOrganization = Utils::coreStringToAppString(vcard->getOrganization()); - mJob = Utils::coreStringToAppString(vcard->getJobTitle()); - mGivenName = Utils::coreStringToAppString(vcard->getGivenName()); - mFamilyName = Utils::coreStringToAppString(vcard->getFamilyName()); + if (vcard) { + mOrganization = Utils::coreStringToAppString(vcard->getOrganization()); + mJob = Utils::coreStringToAppString(vcard->getJobTitle()); + mGivenName = Utils::coreStringToAppString(vcard->getGivenName()); + mFamilyName = Utils::coreStringToAppString(vcard->getFamilyName()); + } auto addresses = contact->getAddresses(); for (auto &address : addresses) { mAddressList.append( @@ -451,8 +453,8 @@ void FriendCore::remove() { if (mFriendModel) { // Update mFriendModelConnection->invokeToModel([this]() { auto contact = mFriendModel->getFriend(); + // emit CoreModel::getInstance()->friendRemoved(contact); contact->remove(); - emit CoreModel::getInstance()->friendRemoved(); mFriendModelConnection->invokeToCore([this]() { removed(this); }); }); } @@ -506,22 +508,24 @@ void FriendCore::save() { // Save Values to model auto contact = core->createFriend(); auto friendModel = Utils::makeQObject_ptr(contact); friendModel->setSelf(friendModel); - mCoreModelConnection->invokeToCore([this, thisCopy, friendModel] { + mCoreModelConnection->invokeToCore([this, thisCopy, friendModel, contact] { mFriendModel = friendModel; - mCoreModelConnection->invokeToModel([this, thisCopy] { + mCoreModelConnection->invokeToModel([this, thisCopy, contact] { + auto core = CoreModel::getInstance()->getCore(); thisCopy->writeIntoModel(mFriendModel); thisCopy->deleteLater(); + bool created = + (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); + if (created) { + core->getDefaultFriendList()->updateSubscriptions(); + emit CoreModel::getInstance()->friendCreated(contact); + } + mCoreModelConnection->invokeToCore([this, created]() { + if (created) setSelf(mCoreModelConnection->mCore); + setIsSaved(created); + }); }); }); - bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); - if (created) { - core->getDefaultFriendList()->updateSubscriptions(); - emit CoreModel::getInstance()->friendAdded(); - } - mCoreModelConnection->invokeToCore([this, created]() { - if (created) setSelf(mCoreModelConnection->mCore); - setIsSaved(created); - }); } }); } diff --git a/Linphone/core/search/MagicSearchList.cpp b/Linphone/core/search/MagicSearchList.cpp index 42d588ebe..fd953cffd 100644 --- a/Linphone/core/search/MagicSearchList.cpp +++ b/Linphone/core/search/MagicSearchList.cpp @@ -72,7 +72,6 @@ void MagicSearchList::setSelf(QSharedPointer me) { &MagicSearchModel::aggregationFlagChanged, [this](LinphoneEnums::MagicSearchAggregation flag) { mModelConnection->invokeToCore([this, flag]() { setAggregationFlag(flag); }); }); - mModelConnection->makeConnectToModel( &MagicSearchModel::searchResultsReceived, [this](const std::list> &results) { @@ -83,12 +82,14 @@ void MagicSearchList::setSelf(QSharedPointer me) { contact = FriendCore::create(it->getFriend()); contacts->append(contact); } else if (auto address = it->getAddress()) { - contact = FriendCore::create(nullptr); + auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend(); + contact = FriendCore::create(linphoneFriend); contact->setGivenName(Utils::coreStringToAppString(address->asStringUriOnly())); contact->appendAddress(Utils::coreStringToAppString(address->asStringUriOnly())); contacts->append(contact); } else if (!it->getPhoneNumber().empty()) { - contact = FriendCore::create(it->getFriend()); + auto linphoneFriend = CoreModel::getInstance()->getCore()->createFriend(); + contact = FriendCore::create(linphoneFriend); contact->setGivenName(Utils::coreStringToAppString(it->getPhoneNumber())); contact->appendPhoneNumber(tr("Phone"), Utils::coreStringToAppString(it->getPhoneNumber())); contacts->append(contact); @@ -99,6 +100,26 @@ void MagicSearchList::setSelf(QSharedPointer me) { delete contacts; }); }); + + mCoreModelConnection = QSharedPointer>( + new SafeConnection(me, CoreModel::getInstance()), &QObject::deleteLater); + mCoreModelConnection->makeConnectToModel( + &CoreModel::friendCreated, [this](const std::shared_ptr &f) { + auto friendCore = FriendCore::create(f); + auto haveContact = + std::find_if(mList.begin(), mList.end(), [friendCore](const QSharedPointer &item) { + return item.objectCast()->getDefaultAddress() == friendCore->getDefaultAddress(); + }); + if (haveContact == mList.end()) { + connect(friendCore.get(), &FriendCore::removed, this, qOverload(&MagicSearchList::remove)); + add(friendCore); + int index = -1; + get(friendCore.get(), &index); + if (index != -1) { + emit friendCreated(index); + } + } + }); } void MagicSearchList::setResults(const QList> &contacts) { @@ -109,6 +130,9 @@ void MagicSearchList::setResults(const QList> &contac add(contacts); } +void MagicSearchList::addResult(const QSharedPointer &contact) { +} + void MagicSearchList::setSearch(const QString &search) { if (!search.isEmpty()) { lSearch(search); diff --git a/Linphone/core/search/MagicSearchList.hpp b/Linphone/core/search/MagicSearchList.hpp index 743eafef4..0a68d0661 100644 --- a/Linphone/core/search/MagicSearchList.hpp +++ b/Linphone/core/search/MagicSearchList.hpp @@ -42,6 +42,7 @@ public: void setSelf(QSharedPointer me); void setSearch(const QString &search); void setResults(const QList> &contacts); + void addResult(const QSharedPointer &contact); int getSourceFlags() const; void setSourceFlags(int flags); @@ -59,6 +60,8 @@ signals: void sourceFlagsChanged(int sourceFlags); void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation flag); + void friendCreated(int index); + private: int mSourceFlags; LinphoneEnums::MagicSearchAggregation mAggregationFlag; diff --git a/Linphone/core/search/MagicSearchProxy.cpp b/Linphone/core/search/MagicSearchProxy.cpp index c2acf83b7..d8a54ec19 100644 --- a/Linphone/core/search/MagicSearchProxy.cpp +++ b/Linphone/core/search/MagicSearchProxy.cpp @@ -20,14 +20,17 @@ #include "MagicSearchProxy.hpp" #include "MagicSearchList.hpp" +#include "core/friend/FriendGui.hpp" MagicSearchProxy::MagicSearchProxy(QObject *parent) : SortFilterProxy(parent) { mList = MagicSearchList::create(); connect(mList.get(), &MagicSearchList::sourceFlagsChanged, this, &MagicSearchProxy::sourceFlagsChanged); connect(mList.get(), &MagicSearchList::aggregationFlagChanged, this, &MagicSearchProxy::aggregationFlagChanged); + connect(mList.get(), &MagicSearchList::friendCreated, this, [this](int index) { + auto proxyIndex = mapFromSource(sourceModel()->index(index, 0)); + emit friendCreated(proxyIndex.row()); + }); setSourceModel(mList.get()); - connect(CoreModel::getInstance().get(), &CoreModel::friendRemoved, this, - [this] { emit mList->lSearch(mSearchText); }); connect(this, &MagicSearchProxy::forceUpdate, [this] { emit mList->lSearch(mSearchText); }); sort(0); } @@ -58,4 +61,19 @@ LinphoneEnums::MagicSearchAggregation MagicSearchProxy::getAggregationFlag() con void MagicSearchProxy::setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag) { qobject_cast(sourceModel())->lSetAggregationFlag(flag); +} + +bool MagicSearchProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { + auto l = sourceModel()->data(left); + auto r = sourceModel()->data(right); + + auto lIsFriend = l.value(); + auto rIsFriend = r.value(); + + if (lIsFriend && rIsFriend) { + auto lName = lIsFriend->getCore()->getDisplayName().toLower(); + auto rName = rIsFriend->getCore()->getDisplayName().toLower(); + return lName < rName; + } + return true; } \ No newline at end of file diff --git a/Linphone/core/search/MagicSearchProxy.hpp b/Linphone/core/search/MagicSearchProxy.hpp index 7284abb08..2a87e97b1 100644 --- a/Linphone/core/search/MagicSearchProxy.hpp +++ b/Linphone/core/search/MagicSearchProxy.hpp @@ -55,10 +55,12 @@ signals: void sourceFlagsChanged(int sourceFlags); void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag); void forceUpdate(); + void friendCreated(int index); protected: QString mSearchText; QSharedPointer mList; + virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; }; #endif diff --git a/Linphone/data/image/video-conference-selected.svg b/Linphone/data/image/video-conference-selected.svg new file mode 100644 index 000000000..a5a854997 --- /dev/null +++ b/Linphone/data/image/video-conference-selected.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Linphone/data/image/video-conference.svg b/Linphone/data/image/video-conference.svg new file mode 100644 index 000000000..5f7f30001 --- /dev/null +++ b/Linphone/data/image/video-conference.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/model/call/CallModel.cpp b/Linphone/model/call/CallModel.cpp index 9202082c6..c0aea1a68 100644 --- a/Linphone/model/call/CallModel.cpp +++ b/Linphone/model/call/CallModel.cpp @@ -185,7 +185,6 @@ void CallModel::stopRecording() { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mMonitor->stopRecording(); emit recordingChanged(mMonitor->getParams()->isRecording()); - // TODO : display notification } void CallModel::setRecordFile(const std::string &path) { diff --git a/Linphone/model/conference/ConferenceInfoModel.cpp b/Linphone/model/conference/ConferenceInfoModel.cpp index f83339f68..8280dc0c0 100644 --- a/Linphone/model/conference/ConferenceInfoModel.cpp +++ b/Linphone/model/conference/ConferenceInfoModel.cpp @@ -45,6 +45,10 @@ void ConferenceInfoModel::createConferenceScheduler() { mustBeInLinphoneThread(getClassName() + "::createConferenceScheduler()"); } +std::shared_ptr ConferenceInfoModel::getConferenceInfo() const { + return mConferenceInfo; +} + std::shared_ptr ConferenceInfoModel::getConferenceScheduler() const { return mConferenceSchedulerModel; } @@ -106,7 +110,9 @@ QString ConferenceInfoModel::getOrganizerName() const { } QString ConferenceInfoModel::getOrganizerAddress() const { - return Utils::coreStringToAppString(mConferenceInfo->getOrganizer()->asStringUriOnly()); + if (auto organizer = mConferenceInfo->getOrganizer()) + return Utils::coreStringToAppString(organizer->asStringUriOnly()); + return QString(); } QString ConferenceInfoModel::getDescription() const { @@ -118,7 +124,7 @@ QString ConferenceInfoModel::getUri() const { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); if (auto uriAddr = mConferenceInfo->getUri()) { return Utils::coreStringToAppString(uriAddr->asString()); - } else return QString(); + } else return mConferenceSchedulerModel->getUri(); } std::list> ConferenceInfoModel::getParticipantInfos() const { diff --git a/Linphone/model/conference/ConferenceInfoModel.hpp b/Linphone/model/conference/ConferenceInfoModel.hpp index 84829f500..34db17b63 100644 --- a/Linphone/model/conference/ConferenceInfoModel.hpp +++ b/Linphone/model/conference/ConferenceInfoModel.hpp @@ -36,6 +36,8 @@ public: void createConferenceScheduler(); + std::shared_ptr getConferenceInfo() const; + std::shared_ptr getConferenceScheduler() const; void setConferenceScheduler(const std::shared_ptr &model); QDateTime getDateTime() const; diff --git a/Linphone/model/conference/ConferenceModel.cpp b/Linphone/model/conference/ConferenceModel.cpp index 8b91f12a5..82d380b8c 100644 --- a/Linphone/model/conference/ConferenceModel.cpp +++ b/Linphone/model/conference/ConferenceModel.cpp @@ -86,8 +86,6 @@ void ConferenceModel::startRecording() { void ConferenceModel::stopRecording() { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mMonitor->stopRecording(); - // emit recordingChanged(mMonitor->getParams()->isRecording()); - // TODO : display notification } void ConferenceModel::setRecordFile(const std::string &path) { diff --git a/Linphone/model/conference/ConferenceSchedulerModel.cpp b/Linphone/model/conference/ConferenceSchedulerModel.cpp index 0abf6f4ad..5182f5164 100644 --- a/Linphone/model/conference/ConferenceSchedulerModel.cpp +++ b/Linphone/model/conference/ConferenceSchedulerModel.cpp @@ -48,6 +48,10 @@ QString ConferenceSchedulerModel::getUri() { } else return QString(); } +linphone::ConferenceScheduler::State ConferenceSchedulerModel::getState() const { + return mState; +} + void ConferenceSchedulerModel::setInfo(const std::shared_ptr &confInfo) { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); mMonitor->setInfo(confInfo); @@ -60,6 +64,7 @@ void ConferenceSchedulerModel::cancelConference(const std::shared_ptr &conferenceScheduler, linphone::ConferenceScheduler::State state) { + mState = state; emit stateChanged(state); } diff --git a/Linphone/model/conference/ConferenceSchedulerModel.hpp b/Linphone/model/conference/ConferenceSchedulerModel.hpp index 4723f912c..2b6047ae1 100644 --- a/Linphone/model/conference/ConferenceSchedulerModel.hpp +++ b/Linphone/model/conference/ConferenceSchedulerModel.hpp @@ -39,6 +39,7 @@ public: ~ConferenceSchedulerModel(); QString getUri(); + linphone::ConferenceScheduler::State getState() const; void setInfo(const std::shared_ptr &confInfo); void cancelConference(const std::shared_ptr &confInfo); @@ -48,6 +49,7 @@ signals: private: DECLARE_ABSTRACT_OBJECT + linphone::ConferenceScheduler::State mState; //-------------------------------------------------------------------------------- // LINPHONE diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index e5fe65865..071eaf136 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -56,8 +56,9 @@ public: signals: void loggerInitialized(); - void friendAdded(); - void friendRemoved(); + void friendCreated(const std::shared_ptr &f); + void friendRemoved(const std::shared_ptr &f); + void conferenceInfoCreated(const std::shared_ptr &confInfo); void unreadNotificationsChanged(); private: diff --git a/Linphone/model/friend/FriendModel.cpp b/Linphone/model/friend/FriendModel.cpp index 04eeaa0ae..da917db51 100644 --- a/Linphone/model/friend/FriendModel.cpp +++ b/Linphone/model/friend/FriendModel.cpp @@ -150,70 +150,90 @@ void FriendModel::setName(const QString &name) { QString FriendModel::getGivenName() const { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); } - return Utils::coreStringToAppString(mMonitor->getVcard()->getGivenName()); + if (mMonitor->getVcard()) return Utils::coreStringToAppString(mMonitor->getVcard()->getGivenName()); + else return QString(); } void FriendModel::setGivenName(const QString &name) { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); + } + if (mMonitor->getVcard()) { + mMonitor->getVcard()->setGivenName(Utils::appStringToCoreString(name)); + emit givenNameChanged(name); } - mMonitor->getVcard()->setGivenName(Utils::appStringToCoreString(name)); - emit givenNameChanged(name); } QString FriendModel::getFamilyName() const { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); } - return Utils::coreStringToAppString(mMonitor->getVcard()->getFamilyName()); + if (mMonitor->getVcard()) return Utils::coreStringToAppString(mMonitor->getVcard()->getFamilyName()); + else return QString(); } void FriendModel::setFamilyName(const QString &name) { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); + } + if (mMonitor->getVcard()) { + mMonitor->getVcard()->setFamilyName(Utils::appStringToCoreString(name)); + emit familyNameChanged(name); } - mMonitor->getVcard()->setFamilyName(Utils::appStringToCoreString(name)); - emit familyNameChanged(name); } QString FriendModel::getOrganization() const { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); } - return Utils::coreStringToAppString(mMonitor->getVcard()->getOrganization()); + if (mMonitor->getVcard()) return Utils::coreStringToAppString(mMonitor->getVcard()->getOrganization()); + else return QString(); } void FriendModel::setOrganization(const QString &orga) { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); + } + if (mMonitor->getVcard()) { + mMonitor->getVcard()->setOrganization(Utils::appStringToCoreString(orga)); + emit organizationChanged(orga); } - mMonitor->getVcard()->setOrganization(Utils::appStringToCoreString(orga)); - emit organizationChanged(orga); } QString FriendModel::getJob() const { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); } - return Utils::coreStringToAppString(mMonitor->getVcard()->getJobTitle()); + if (mMonitor->getVcard()) return Utils::coreStringToAppString(mMonitor->getVcard()->getJobTitle()); + else return QString(); } void FriendModel::setJob(const QString &job) { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); + } + if (mMonitor->getVcard()) { + mMonitor->getVcard()->setJobTitle(Utils::appStringToCoreString(job)); + emit jobChanged(job); } - mMonitor->getVcard()->setJobTitle(Utils::appStringToCoreString(job)); - emit jobChanged(job); } bool FriendModel::getStarred() const { @@ -231,10 +251,12 @@ void FriendModel::onPresenceReceived(const std::shared_ptr &co QString FriendModel::getPictureUri() const { auto vcard = mMonitor->getVcard(); + bool created = false; if (!vcard) { - mMonitor->createVcard(mMonitor->getName()); + created = mMonitor->createVcard(mMonitor->getName()); } - return Utils::coreStringToAppString(mMonitor->getVcard()->getPhoto()); + if (mMonitor->getVcard()) return Utils::coreStringToAppString(mMonitor->getVcard()->getPhoto()); + else return QString(); } void FriendModel::setPictureUri(const QString &uri) { diff --git a/Linphone/model/search/MagicSearchModel.cpp b/Linphone/model/search/MagicSearchModel.cpp index bd9f50177..e89d3e9b9 100644 --- a/Linphone/model/search/MagicSearchModel.cpp +++ b/Linphone/model/search/MagicSearchModel.cpp @@ -30,11 +30,6 @@ DEFINE_ABSTRACT_OBJECT(MagicSearchModel) MagicSearchModel::MagicSearchModel(const std::shared_ptr &data, QObject *parent) : ::Listener(data, parent) { mustBeInLinphoneThread(getClassName()); - // Removed is managed by FriendCore that allow to remove result automatically. - // No need to restart a new search in this case - connect(CoreModel::getInstance().get(), &CoreModel::friendAdded, this, [this]() { - if (!mLastSearch.isEmpty()) search(mLastSearch); - }); } MagicSearchModel::~MagicSearchModel() { diff --git a/Linphone/tool/LinphoneEnums.hpp b/Linphone/tool/LinphoneEnums.hpp index 21d2a3eef..dfca4e929 100644 --- a/Linphone/tool/LinphoneEnums.hpp +++ b/Linphone/tool/LinphoneEnums.hpp @@ -187,9 +187,9 @@ linphone::ConferenceInfo::State toLinphone(const LinphoneEnums::ConferenceInfoSt LinphoneEnums::ConferenceInfoState fromLinphone(const linphone::ConferenceInfo::State &state); enum class ConferenceSchedulerState { - AllocationPending = int(linphone::ConferenceScheduler::State::AllocationPending), - Error = int(linphone::ConferenceScheduler::State::Error), Idle = int(linphone::ConferenceScheduler::State::Idle), + Error = int(linphone::ConferenceScheduler::State::Error), + AllocationPending = int(linphone::ConferenceScheduler::State::AllocationPending), Ready = int(linphone::ConferenceScheduler::State::Ready), Updating = int(linphone::ConferenceScheduler::State::Updating) }; diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index 0ea13e0be..ff1e59e49 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -111,6 +111,9 @@ void Utils::createCall(const QString &sipAddress, // TODO : change conf info only from qml // (bug si on est déjà en appel et qu'on lance une conf) +// demander à jonhatan pour le design : quand on est déjà en appel +// et qu'on join une conf on retourne donc sur la waiting room +// Comment on annule ? Si on ferme la fenêtre ça va finir l'appel en cours void Utils::setupConference(ConferenceInfoGui *confGui) { if (!confGui) return; auto window = App::getInstance()->getCallsWindow(QVariant()); @@ -1124,6 +1127,10 @@ QString Utils::toDateMonthString(const QDateTime &date) { return QLocale().toString(date, "MMMM"); } +QString Utils::toDateMonthAndYearString(const QDateTime &date) { + return QLocale().toString(date, "MMMM yyyy"); +} + bool Utils::isCurrentDay(QDateTime date) { auto dateDayNum = date.date().day(); auto currentDate = QDateTime::currentDateTime(); @@ -1152,6 +1159,10 @@ bool Utils::datesAreEqual(const QDate &a, const QDate &b) { return a.month() == b.month() && a.year() == b.year() && a.day() == b.day(); } +bool Utils::dateisInMonth(const QDate &a, int month, int year) { + return a.month() == month && a.year() == year; +} + QDateTime Utils::createDateTime(const QDate &date, int hour, int min) { QTime time(hour, min); return QDateTime(date, time); @@ -1177,6 +1188,11 @@ QDateTime Utils::addSecs(QDateTime date, int secs) { return date; } +QDateTime Utils::addYears(QDateTime date, int years) { + date = date.addYears(years); + return date; +} + int Utils::getYear(const QDate &date) { return date.year(); } diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index ff2d2540a..2f4a7bf75 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -87,17 +87,20 @@ public: Q_INVOKABLE static QString toDateHourString(const QDateTime &date); Q_INVOKABLE static QString toDateDayNameString(const QDateTime &date); Q_INVOKABLE static QString toDateMonthString(const QDateTime &date); + Q_INVOKABLE static QString toDateMonthAndYearString(const QDateTime &date); Q_INVOKABLE static bool isCurrentDay(QDateTime date); Q_INVOKABLE static bool isCurrentDay(QDate date); Q_INVOKABLE static bool isCurrentMonth(QDate date); Q_INVOKABLE static bool isBeforeToday(QDate date); Q_INVOKABLE static bool datesAreEqual(const QDate &a, const QDate &b); + Q_INVOKABLE static bool dateisInMonth(const QDate &a, int month, int year); Q_INVOKABLE static QDateTime createDateTime(const QDate &date, int hour, int min); Q_INVOKABLE static QDateTime getCurrentDateTime(); Q_INVOKABLE static QDateTime getCurrentDateTimeUtc(); Q_INVOKABLE static int getYear(const QDate &date); Q_INVOKABLE static int secsTo(const QString &start, const QString &end); Q_INVOKABLE static QDateTime addSecs(QDateTime date, int secs); + Q_INVOKABLE static QDateTime addYears(QDateTime date, int years); Q_INVOKABLE static QString generateLinphoneSipAddress(const QString &uri); Q_INVOKABLE static QString findAvatarByAddress(const QString &address); static QString generateSavedFilename(const QString &from, const QString &to); diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index de4926f45..eab009a8d 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -275,7 +275,6 @@ Window { Layout.preferredWidth: 30 * DefaultStyle.dp Layout.preferredHeight: 30 * DefaultStyle.dp // TODO : change with broadcast or meeting icon when available - // + imageSource: !mainWindow.call ? AppIcons.meeting : (mainWindow.callState === LinphoneEnums.CallState.End @@ -725,18 +724,16 @@ Window { Connections { target: participantsStack onCurrentItemChanged: { - console.log("changing title", participantsStack.currentItem == participantList) if (participantsStack.currentItem == participantList) rightPanel.headerTitleText = qsTr("Participants (%1)").arg(participantList.count) } } Connections { target: rightPanel - // TODO : chercher comment relier ces infos pour faire le add des participants - //onValidateRequested: { - // participantList.model.addAddresses(participantsStack.selectedParticipants) - // participantsStack.pop() - // participantsStack.participantAdded() - //} + onValidateRequested: { + participantList.model.addAddresses(participantsStack.selectedParticipants) + participantsStack.pop() + participantsStack.participantAdded() + } } } } diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 66a3ed539..cf264bf28 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -26,6 +26,11 @@ Item { transferSucceedPopup.open() } + function createContact(name, address) { + tabbar.currentIndex = 1 + contactPage.createContact(name, address) + } + Timer { id: autoClosePopup interval: 5000 @@ -119,7 +124,7 @@ Item { {icon: AppIcons.phone, selectedIcon: AppIcons.phoneSelected, label: qsTr("Appels")}, {icon: AppIcons.adressBook, selectedIcon: AppIcons.adressBookSelected, label: qsTr("Contacts")}, {icon: AppIcons.chatTeardropText, selectedIcon: AppIcons.chatTeardropTextSelected, label: qsTr("Conversations")}, - {icon: AppIcons.usersThree, selectedIcon: AppIcons.usersThreeSelected, label: qsTr("Réunions")} + {icon: AppIcons.videoconference, selectedIcon: AppIcons.videoconferenceSelected, label: qsTr("Réunions")} ] } @@ -269,9 +274,8 @@ Item { Item {Layout.fillWidth: true} } onClicked: { - var currentItem = mainStackLayout.children[mainStackLayout.currentIndex] + mainItem.createContact(magicSearchBar.text, sipAddr.text) listPopup.close() - currentItem.createContact(magicSearchBar.text, sipAddr.text) } } } @@ -360,8 +364,13 @@ Item { Layout.topMargin: 24 * DefaultStyle.dp CallPage { id: callPage + onCreateContactRequested: (name, address) => { + mainItem.createContact(name, address) + } + } + ContactPage{ + id: contactPage } - ContactPage{} Item{} //ConversationPage{} MeetingPage{} diff --git a/Linphone/view/App/Main.qml b/Linphone/view/App/Main.qml index f1e63f4ea..1c8d12fd9 100644 --- a/Linphone/view/App/Main.qml +++ b/Linphone/view/App/Main.qml @@ -33,6 +33,10 @@ ApplicationWindow { mainWindowStackView.currentItem.transferCallSucceed() } + function removeFromPopupLayout(index) { + popupLayout.popupList.splice(index, 1) + } + Component { id: popupComp InformationPopup{} @@ -42,26 +46,44 @@ ApplicationWindow { infoPopup.index = popupLayout.popupList.length popupLayout.popupList.push(infoPopup) infoPopup.open() + infoPopup.closePopup.connect(removeFromPopupLayout) + } + function showLoadingPopup(text) { + loadingPopup.text = text + loadingPopup.open() + } + function closeLoadingPopup() { + loadingPopup.close() } - ColumnLayout { id: popupLayout anchors.fill: parent Layout.alignment: Qt.AlignBottom property int nextY: mainWindow.height - property list popupList + property list popupList property int popupCount: popupList.length - spacing: 15 + spacing: 15 * DefaultStyle.dp onPopupCountChanged: { nextY = mainWindow.height for(var i = 0; i < popupCount; ++i) { popupList[i].y = nextY - popupList[i].height + popupList[i].index = i nextY = nextY - popupList[i].height - 15 } } } + LoadingPopup { + id: loadingPopup + modal: true + closePolicy: Popup.NoAutoClose + anchors.centerIn: parent + padding: 20 * DefaultStyle.dp + underlineColor: DefaultStyle.main1_500_main + radius: 15 * DefaultStyle.dp + } + AccountProxy { // TODO : change this so it does not display the main page for one second // when we fail trying to connect the first account (account is added and diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 9d711905b..5f59ef644 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -14,6 +14,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Layout/FormItemLayout.qml view/Layout/Mosaic.qml + view/Layout/RightPanelLayout.qml view/Layout/Section.qml view/Item/Account/Accounts.qml @@ -58,6 +59,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/ErrorText.qml view/Item/IconLabelButton.qml view/Item/InformationPopup.qml + view/Item/LoadingPopup.qml view/Item/MovableMouseArea.qml view/Item/NumericPad.qml view/Item/PhoneNumberComboBox.qml diff --git a/Linphone/view/Item/Calendar.qml b/Linphone/view/Item/Calendar.qml index de98b8433..7202b9e73 100644 --- a/Linphone/view/Item/Calendar.qml +++ b/Linphone/view/Item/Calendar.qml @@ -7,35 +7,39 @@ import Linphone import ConstantsCpp 1.0 import UtilsCpp 1.0 -// TODO : spacing ListView { id: mainItem - // width: 400 * DefaultStyle.dp - // height: 400 * DefaultStyle.dp snapMode: ListView.SnapOneItem orientation: Qt.Horizontal clip: true property int maxYears: 5 readonly property var currentDate: new Date() highlightMoveDuration: 100 + // height: contentHeight property var selectedDate - + + property int currentMonth: calendarModel.monthAt(currentIndex) + 1 //january is index 0 + property int currentYear: calendarModel.yearAt(currentIndex) + onCurrentYearChanged: console.log("currentyear", currentYear) + onCurrentMonthChanged: console.log("current month", currentMonth) + model: Control.CalendarModel { id: calendarModel from: new Date() - // TODO : dynamically add 5 years - to: new Date(2025, 12, 31) + to: UtilsCpp.addYears(new Date(), 5) } delegate: ColumnLayout { width: mainItem.width height: mainItem.height + property int currentMonth: model.month + spacing: 18 * DefaultStyle.dp RowLayout { Layout.fillWidth: true + spacing: 38 * DefaultStyle.dp Text { - // TODO (high prio): don't use javascript, C++ - text: new Date(model.year, model.month, 15).toLocaleString(Qt.locale(ConstantsCpp.DefaultLocale), 'MMMM yyyy')// 15 because of timezones that can change the date for localeString + text: UtilsCpp.toDateMonthAndYearString(new Date(model.year, model.month, 15))// 15 because of timezones that can change the date for localeString font { pixelSize: 14 * DefaultStyle.dp weight: 700 * DefaultStyle.dp @@ -52,7 +56,7 @@ ListView { icon.height: height background: Item{} icon.source: AppIcons.leftArrow - onClicked: if (mainItem.currentIndex > 0) --mainItem.currentIndex + onClicked: if (mainItem.currentIndex > 0) mainItem.currentIndex = mainItem.currentIndex - 1 } Button { Layout.preferredWidth: 20 * DefaultStyle.dp @@ -61,62 +65,69 @@ ListView { icon.height: height background: Item{} icon.source: AppIcons.rightArrow - onClicked: if (mainItem.currentIndex < mainItem.count) ++mainItem.currentIndex - } - } - Control.DayOfWeekRow { - locale: monthGrid.locale - Layout.column: 1 - Layout.fillWidth: true - delegate: Text { - text: model.shortName - color: DefaultStyle.main2_400 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font { - pixelSize: 12 * DefaultStyle.dp - weight: 300 * DefaultStyle.dp - } + onClicked: if (mainItem.currentIndex < mainItem.count) mainItem.currentIndex = mainItem.currentIndex + 1 } } - Control.MonthGrid { - id: monthGrid - Layout.fillWidth: true - Layout.fillHeight: true - - year: model.year - month: model.month - // locale: Qt.locale("en_US") - delegate: Item { - property bool isSelectedDay: mainItem.selectedDate ? UtilsCpp.datesAreEqual(mainItem.selectedDate, model.date) : false - Rectangle { - anchors.centerIn: parent - width: 30 * DefaultStyle.dp - height: 30 * DefaultStyle.dp - radius: 50 * DefaultStyle.dp - color: isSelectedDay ? DefaultStyle.main1_500_main : "transparent" - } - Text { - anchors.centerIn: parent - text: monthGrid.locale.toString(model.date, "d") - color: isSelectedDay - ? DefaultStyle.grey_0 - : UtilsCpp.isCurrentDay(model.date) - ? DefaultStyle.main1_500_main - : UtilsCpp.isCurrentMonth(model.date) - ? DefaultStyle.main2_700 - : DefaultStyle.main2_400 + ColumnLayout { + spacing: 12 * DefaultStyle.dp + Control.DayOfWeekRow { + locale: monthGrid.locale + Layout.column: 1 + Layout.fillWidth: true + delegate: Text { + text: model.shortName + color: DefaultStyle.main2_400 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter font { pixelSize: 12 * DefaultStyle.dp weight: 300 * DefaultStyle.dp } } } - onClicked: (date) => { - if (UtilsCpp.isBeforeToday(date)) return; - mainItem.selectedDate = date - } - } + + Control.MonthGrid { + id: monthGrid + Layout.fillWidth: true + Layout.fillHeight: true + year: model.year + month: model.month + property var curDate: model.date + onMonthChanged: console.log("cur date changed", month) + locale: Qt.locale(ConstantsCpp.DefaultLocale) + delegate: Item { + property bool isSelectedDay: mainItem.selectedDate ? UtilsCpp.datesAreEqual(mainItem.selectedDate, model.date) : false + // width: 30 * DefaultStyle.dp + // height: 30 * DefaultStyle.dp + Rectangle { + anchors.centerIn: parent + width: 30 * DefaultStyle.dp + height: 30 * DefaultStyle.dp + radius: 50 * DefaultStyle.dp + color: isSelectedDay ? DefaultStyle.main1_500_main : "transparent" + } + Text { + anchors.centerIn: parent + text: UtilsCpp.toDateDayString(model.date) + color: isSelectedDay + ? DefaultStyle.grey_0 + : UtilsCpp.isCurrentDay(model.date) + ? DefaultStyle.main1_500_main + : UtilsCpp.dateisInMonth(model.date, mainItem.currentMonth, mainItem.currentYear) + ? DefaultStyle.main2_700 + : DefaultStyle.main2_400 + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + } + onClicked: (date) => { + if (UtilsCpp.isBeforeToday(date)) return; + mainItem.selectedDate = date + } + } + } } } \ No newline at end of file diff --git a/Linphone/view/Item/CalendarComboBox.qml b/Linphone/view/Item/CalendarComboBox.qml index 1ec517d5f..5b2768f27 100644 --- a/Linphone/view/Item/CalendarComboBox.qml +++ b/Linphone/view/Item/CalendarComboBox.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls as Control import QtQuick.Effects +import QtQuick.Layouts import Linphone ComboBox { @@ -25,7 +26,12 @@ ComboBox { width: 321 * DefaultStyle.dp height: 270 * DefaultStyle.dp closePolicy: Popup.NoAutoClose + topPadding: 25 * DefaultStyle.dp + bottomPadding: 24 * DefaultStyle.dp + leftPadding: 21 * DefaultStyle.dp + rightPadding: 19 * DefaultStyle.dp background: Item { + anchors.fill: parent Rectangle { id: calendarBg anchors.fill: parent diff --git a/Linphone/view/Item/CheckableButton.qml b/Linphone/view/Item/CheckableButton.qml index 7e8b83c03..26a5617a5 100644 --- a/Linphone/view/Item/CheckableButton.qml +++ b/Linphone/view/Item/CheckableButton.qml @@ -19,7 +19,7 @@ Button { background: Rectangle { anchors.fill: parent color: mainItem.backgroundColor - radius: mainItem.width * 1.29 + radius: mainItem.width /2 } icon.source: checkedIconUrl && mainItem.checked ? checkedIconUrl : iconUrl icon.width: width * 0.58 diff --git a/Linphone/view/Item/Contact/ContactEdition.qml b/Linphone/view/Item/Contact/ContactEdition.qml index 98b2e9e4d..0588b492a 100644 --- a/Linphone/view/Item/Contact/ContactEdition.qml +++ b/Linphone/view/Item/Contact/ContactEdition.qml @@ -7,265 +7,281 @@ import QtQuick.Layouts import Linphone import UtilsCpp 1.0 -ColumnLayout { +RightPanelLayout { id: mainItem property FriendGui contact + Connections { + target: contact.core + onIsSavedChanged: { + if (contact.core.isSaved) { + var mainWin = UtilsCpp.getMainWindow() + UtilsCpp.smartShowWindow(mainWin) + mainWin.goToContactDetail(contact) + } + } + } property string title: qsTr("Modifier contact") property string saveButtonText: qsTr("Enregistrer") property string oldPictureUri signal closeEdition() - Rectangle { - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.fillWidth: true - Layout.preferredHeight: 40 * DefaultStyle.dp - Text { - anchors.left: parent.left - anchors.leftMargin: 10 * DefaultStyle.dp - text: mainItem.title - font { - pixelSize: 20 * DefaultStyle.dp - weight: 800 * DefaultStyle.dp + headerContent: [ + Text { + anchors.left: parent.left + anchors.leftMargin: 31 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + text: mainItem.title + font { + pixelSize: 20 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + }, + Button { + background: Item{} + anchors.right: parent.right + anchors.rightMargin: 41 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + icon.source: AppIcons.closeX + icon.width: 24 * DefaultStyle.dp + icon.height: 24 * DefaultStyle.dp + onClicked: { + // contact.core.pictureUri = mainItem.oldPictureUri + mainItem.contact.core.undo() + mainItem.closeEdition() + } } - } - Button { - background: Item{} - anchors.right: parent.right - anchors.rightMargin: 10 * DefaultStyle.dp - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - icon.source: AppIcons.closeX - icon.width: 24 * DefaultStyle.dp - icon.height: 24 * DefaultStyle.dp - onClicked: { - // contact.core.pictureUri = mainItem.oldPictureUri - mainItem.contact.core.undo() - mainItem.closeEdition() - } - } + ] - } - ColumnLayout { - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 69 * DefaultStyle.dp - Avatar { - contact: mainItem.contact - Layout.preferredWidth: 72 * DefaultStyle.dp - Layout.preferredHeight: 72 * DefaultStyle.dp - Layout.alignment: Qt.AlignHCenter - } - IconLabelButton { - visible: !mainItem.contact || mainItem.contact.core.pictureUri.length === 0 - Layout.preferredWidth: width - Layout.preferredHeight: 17 * DefaultStyle.dp - iconSource: AppIcons.camera - iconSize: 17 * DefaultStyle.dp - text: qsTr("Ajouter une image") - onClicked: fileDialog.open() - } - RowLayout { - visible: mainItem.contact && mainItem.contact.core.pictureUri.length != 0 + content: ColumnLayout { + anchors.centerIn: parent + // anchors.leftMargin: 103 * DefaultStyle.dp + ColumnLayout { Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 69 * DefaultStyle.dp + Avatar { + contact: mainItem.contact + Layout.preferredWidth: 72 * DefaultStyle.dp + Layout.preferredHeight: 72 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + } IconLabelButton { + visible: !mainItem.contact || mainItem.contact.core.pictureUri.length === 0 Layout.preferredWidth: width Layout.preferredHeight: 17 * DefaultStyle.dp - iconSource: AppIcons.pencil + iconSource: AppIcons.camera iconSize: 17 * DefaultStyle.dp - text: qsTr("Modifier") + text: qsTr("Ajouter une image") onClicked: fileDialog.open() } - FileDialog { - id: fileDialog - currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] - onAccepted: { - mainItem.oldPictureUri = mainItem.contact.core.pictureUri - var avatarPath = UtilsCpp.createAvatar( selectedFile ) - if(avatarPath){ - mainItem.contact.core.pictureUri = avatarPath + RowLayout { + visible: mainItem.contact && mainItem.contact.core.pictureUri.length != 0 + Layout.alignment: Qt.AlignHCenter + IconLabelButton { + Layout.preferredWidth: width + Layout.preferredHeight: 17 * DefaultStyle.dp + iconSource: AppIcons.pencil + iconSize: 17 * DefaultStyle.dp + text: qsTr("Modifier") + onClicked: fileDialog.open() + } + FileDialog { + id: fileDialog + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + mainItem.oldPictureUri = mainItem.contact.core.pictureUri + var avatarPath = UtilsCpp.createAvatar( selectedFile ) + if(avatarPath){ + mainItem.contact.core.pictureUri = avatarPath + } } } - } - IconLabelButton { - Layout.preferredHeight: 17 * DefaultStyle.dp - Layout.preferredWidth: width - iconSize: 17 * DefaultStyle.dp - iconSource: AppIcons.trashCan - text: qsTr("Supprimer") - onClicked: mainItem.contact.core.pictureUri = "" + IconLabelButton { + Layout.preferredHeight: 17 * DefaultStyle.dp + Layout.preferredWidth: width + iconSize: 17 * DefaultStyle.dp + iconSource: AppIcons.trashCan + text: qsTr("Supprimer") + onClicked: mainItem.contact.core.pictureUri = "" + } } } - } - RowLayout { - Layout.alignment: Qt.AlignHCenter - Layout.fillHeight: true - Layout.fillWidth: true - spacing: 100 * DefaultStyle.dp - Layout.topMargin: 50 * DefaultStyle.dp - Layout.bottomMargin: 50 * DefaultStyle.dp - ColumnLayout { - spacing: 20 * DefaultStyle.dp - FormItemLayout { - label: qsTr("Prénom") - contentItem: TextField { - initialText: contact.core.givenName - onEditingFinished: contact.core.givenName = text - backgroundColor: DefaultStyle.grey_0 - } - } - FormItemLayout { - label: qsTr("Nom") - contentItem: TextField { - initialText: contact.core.familyName - onEditingFinished: contact.core.familyName = text - backgroundColor: DefaultStyle.grey_0 - } - } - FormItemLayout { - label: qsTr("Entreprise") - contentItem: TextField { - initialText: contact.core.organization - onEditingFinished: contact.core.organization = text - backgroundColor: DefaultStyle.grey_0 - } - } - FormItemLayout { - label: qsTr("Fonction") - contentItem: TextField { - initialText: contact.core.job - onEditingFinished: contact.core.job = text - backgroundColor: DefaultStyle.grey_0 - } - } - Item{Layout.fillHeight: true} - } - Control.ScrollView { + RowLayout { + Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true - contentHeight: content.height + Layout.fillWidth: true + spacing: 100 * DefaultStyle.dp + Layout.topMargin: 50 * DefaultStyle.dp + Layout.bottomMargin: 50 * DefaultStyle.dp ColumnLayout { - id: content - anchors.rightMargin: 10 * DefaultStyle.dp spacing: 20 * DefaultStyle.dp - Repeater { - model: VariantList { - model: mainItem.contact && mainItem.contact.core.addresses || [] - } - delegate: RowLayout { - FormItemLayout { - label: modelData.label - contentItem: TextField { - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.setAddressAt(index, qsTr("Address SIP"), text) - } - initialText: modelData.address - backgroundColor: DefaultStyle.grey_0 - } - } - Button { - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp - background: Item{} - icon.source: AppIcons.closeX - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - icon.width: 24 * DefaultStyle.dp - icon.height: 24 * DefaultStyle.dp - onClicked: mainItem.contact.core.removeAddress(index) - } + FormItemLayout { + label: qsTr("Prénom") + contentItem: TextField { + initialText: contact.core.givenName + onEditingFinished: contact.core.givenName = text + backgroundColor: DefaultStyle.grey_0 } } - RowLayout { - FormItemLayout { - label: qsTr("Adresse SIP") - contentItem: TextField { - backgroundColor: DefaultStyle.grey_0 - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.appendAddress(text) - text = "" - } - } - } - Item { - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp + FormItemLayout { + label: qsTr("Nom") + contentItem: TextField { + initialText: contact.core.familyName + onEditingFinished: contact.core.familyName = text + backgroundColor: DefaultStyle.grey_0 } } - Repeater { - // phone numbers - model: VariantList { - model: mainItem.contact && mainItem.contact.core.phoneNumbers || [] - } - delegate: RowLayout { - FormItemLayout { - label: modelData.label - contentItem: TextField { - initialText: modelData.address - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.setPhoneNumberAt(index, qsTr("Téléphone"), text) - } - backgroundColor: DefaultStyle.grey_0 - } - } - Button { - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp - background: Item{} - icon.source: AppIcons.closeX - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - icon.width: 24 * DefaultStyle.dp - icon.height: 24 * DefaultStyle.dp - onClicked: mainItem.contact.core.removePhoneNumber(index) - } + FormItemLayout { + label: qsTr("Entreprise") + contentItem: TextField { + initialText: contact.core.organization + onEditingFinished: contact.core.organization = text + backgroundColor: DefaultStyle.grey_0 } } - RowLayout { - FormItemLayout { - label: qsTr("Phone") - contentItem: TextField { - backgroundColor: DefaultStyle.grey_0 - onEditingFinished: { - if (text.length != 0) mainItem.contact.core.appendPhoneNumber(label, text) - setText("") - } - } - } - Item { - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp + FormItemLayout { + label: qsTr("Fonction") + contentItem: TextField { + initialText: contact.core.job + onEditingFinished: contact.core.job = text + backgroundColor: DefaultStyle.grey_0 } } Item{Layout.fillHeight: true} } - Control.ScrollBar.vertical: Control.ScrollBar{ - id: scrollbar - active: true - interactive: true - policy: Control.ScrollBar.AsNeeded - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.leftMargin: 15 * DefaultStyle.dp - } - Control.ScrollBar.horizontal: Control.ScrollBar{ - visible: false + Control.ScrollView { + Layout.fillHeight: true + contentHeight: content.height + ColumnLayout { + id: content + anchors.rightMargin: 10 * DefaultStyle.dp + spacing: 20 * DefaultStyle.dp + Repeater { + model: VariantList { + model: mainItem.contact && mainItem.contact.core.addresses || [] + } + delegate: FormItemLayout { + label: modelData.label + contentItem: RowLayout { + TextField { + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.setAddressAt(index, qsTr("Address SIP"), text) + } + initialText: modelData.address + backgroundColor: DefaultStyle.grey_0 + Layout.preferredWidth: width + Layout.preferredHeight: height + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + Layout.alignment: Qt.AlignVCenter + background: Item{} + icon.source: AppIcons.closeX + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + icon.width: 24 * DefaultStyle.dp + icon.height: 24 * DefaultStyle.dp + onClicked: mainItem.contact.core.removeAddress(index) + } + } + } + } + RowLayout { + FormItemLayout { + label: qsTr("Adresse SIP") + contentItem: TextField { + backgroundColor: DefaultStyle.grey_0 + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.appendAddress(text) + text = "" + } + } + } + Item { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + } + Repeater { + // phone numbers + model: VariantList { + model: mainItem.contact && mainItem.contact.core.phoneNumbers || [] + } + delegate: RowLayout { + FormItemLayout { + label: modelData.label + contentItem: TextField { + initialText: modelData.address + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.setPhoneNumberAt(index, qsTr("Téléphone"), text) + } + backgroundColor: DefaultStyle.grey_0 + } + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + background: Item{} + icon.source: AppIcons.closeX + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + icon.width: 24 * DefaultStyle.dp + icon.height: 24 * DefaultStyle.dp + onClicked: mainItem.contact.core.removePhoneNumber(index) + } + } + } + RowLayout { + FormItemLayout { + label: qsTr("Phone") + contentItem: TextField { + backgroundColor: DefaultStyle.grey_0 + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.appendPhoneNumber(label, text) + setText("") + } + } + } + Item { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + } + Item{Layout.fillHeight: true} + } + Control.ScrollBar.vertical: Control.ScrollBar{ + id: scrollbar + active: true + interactive: true + policy: Control.ScrollBar.AsNeeded + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.leftMargin: 15 * DefaultStyle.dp + } + Control.ScrollBar.horizontal: Control.ScrollBar{ + visible: false + } } } - } - Button { - Layout.bottomMargin: 100 * DefaultStyle.dp - Layout.preferredWidth: 165 * DefaultStyle.dp - Layout.alignment: Qt.AlignHCenter - enabled: mainItem.contact && mainItem.contact.core.givenName.length > 0 - text: mainItem.saveButtonText - leftPadding: 20 * DefaultStyle.dp - rightPadding: 20 * DefaultStyle.dp - topPadding: 11 * DefaultStyle.dp - bottomPadding: 11 * DefaultStyle.dp - onClicked: { - mainItem.contact.core.save() - mainItem.closeEdition() + Button { + Layout.bottomMargin: 100 * DefaultStyle.dp + Layout.preferredWidth: 165 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + enabled: mainItem.contact && mainItem.contact.core.givenName.length > 0 + text: mainItem.saveButtonText + leftPadding: 20 * DefaultStyle.dp + rightPadding: 20 * DefaultStyle.dp + topPadding: 11 * DefaultStyle.dp + bottomPadding: 11 * DefaultStyle.dp + onClicked: { + mainItem.contact.core.save() + mainItem.closeEdition() + } } } -} +} \ No newline at end of file diff --git a/Linphone/view/Item/Contact/ContactsList.qml b/Linphone/view/Item/Contact/ContactsList.qml index d9dca60e0..aff76654d 100644 --- a/Linphone/view/Item/Contact/ContactsList.qml +++ b/Linphone/view/Item/Contact/ContactsList.qml @@ -4,6 +4,7 @@ import QtQuick.Controls as Control import Linphone import UtilsCpp 1.0 +import ConstantsCpp 1.0 ListView { id: mainItem @@ -40,11 +41,7 @@ ListView { property FriendGui selectedContact: model.getAt(currentIndex) || null onCurrentIndexChanged: selectedContact = model.getAt(currentIndex) || null - onCountChanged: { - selectedContact = model.getAt(currentIndex) || null - } - // signal contactSelected(var contact) signal contactStarredChanged() signal contactDeletionRequested(FriendGui contact) signal contactAddedToSelection() @@ -66,6 +63,9 @@ ListView { model: MagicSearchProxy { searchText: searchBarText.length === 0 ? "*" : searchBarText + onFriendCreated: (index) => { + mainItem.currentIndex = index + } } Control.ScrollBar.vertical: ScrollBar { @@ -85,9 +85,10 @@ ListView { property var previousDisplayName: previousItem ? previousItem.core.displayName : "" property var displayName: modelData.core.displayName property bool display: !mainItem.showOnlyFavourites || modelData.core.starred - visible: display + Connections { + enabled: modelData.core target: modelData.core onStarredChanged: mainItem.contactStarredChanged() } @@ -98,7 +99,7 @@ ListView { anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter width: 20 * DefaultStyle.dp - opacity: (!previousItem || !previousDisplayName.startsWith(displayName[0])) ? 1 : 0 + opacity: (!previousItem || !previousDisplayName.toLocaleLowerCase(ConstantsCpp.DefaultLocale).startsWith(displayName[0].toLocaleLowerCase(ConstantsCpp.DefaultLocale))) ? 1 : 0 text: displayName[0] color: DefaultStyle.main2_400 font { diff --git a/Linphone/view/Item/InformationPopup.qml b/Linphone/view/Item/InformationPopup.qml index 8e3ffb9f6..f30501ca7 100644 --- a/Linphone/view/Item/InformationPopup.qml +++ b/Linphone/view/Item/InformationPopup.qml @@ -9,12 +9,11 @@ Popup { property string title property string description property int index + signal closePopup(int index) + onClosed: closePopup(index) onAboutToShow: { autoClosePopup.restart() } - onAboutToHide: { - popupLayout.popupList.splice(mainItem.index, 1) - } closePolicy: Popup.NoAutoClose x : parent.x + parent.width - width // y : parent.y + parent.height - height diff --git a/Linphone/view/Item/LoadingPopup.qml b/Linphone/view/Item/LoadingPopup.qml new file mode 100644 index 000000000..e24053d7a --- /dev/null +++ b/Linphone/view/Item/LoadingPopup.qml @@ -0,0 +1,32 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.3 +import QtQuick.Controls +import Linphone + +Popup { + id: mainItem + property string text + modal: true + closePolicy: Control.Popup.NoAutoClose + anchors.centerIn: parent + padding: 20 * DefaultStyle.dp + underlineColor: DefaultStyle.main1_500_main + radius: 15 * DefaultStyle.dp + // onAboutToShow: width = contentText.implicitWidth + contentItem: ColumnLayout { + spacing: 15 * DefaultStyle.dp + BusyIndicator{ + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 33 * DefaultStyle.dp + Layout.preferredHeight: 33 * DefaultStyle.dp + } + Text { + id: contentText + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.fillHeight: true + text: mainItem.text + font.pixelSize: 14 * DefaultStyle.dp + } + } +} \ No newline at end of file diff --git a/Linphone/view/Item/Meeting/MeetingList.qml b/Linphone/view/Item/Meeting/MeetingList.qml index 8e5e08485..2b101af24 100644 --- a/Linphone/view/Item/Meeting/MeetingList.qml +++ b/Linphone/view/Item/Meeting/MeetingList.qml @@ -14,14 +14,14 @@ ListView { property string searchBarText property bool hoverEnabled: true property var delegateButtons - property ConferenceInfoGui selectedConference: model.getAt(currentIndex) || null + property ConferenceInfoGui selectedConference: currentIndex != -1 ? model.getAt(currentIndex) : null spacing: 8 * DefaultStyle.dp currentIndex: confInfoProxy.currentDateIndex - onCountChanged: selectedConference = model.getAt(currentIndex) || null + onCountChanged: selectedConference = currentIndex != -1 ? model.getAt(currentIndex) : null onCurrentIndexChanged: { - selectedConference = currentIndex != confInfoProxy.currentDateIndex ? model.getAt(currentIndex) : null + selectedConference = model.getAt(currentIndex) } onVisibleChanged: if( visible) { mainItem.positionViewAtIndex(currentIndex, ListView.Center)// First approximative move @@ -34,16 +34,13 @@ ListView { } // using highlight doesn't center, take time before moving and don't work for not visible item (like not loaded) highlightFollowsCurrentItem: false - - function forceUpdate() { - confInfoProxy.lUpdate() - } signal conferenceSelected(var contact) model: ConferenceInfoProxy { id: confInfoProxy searchText: searchBarText.length === 0 ? "" : searchBarText + filterType: ConferenceInfoProxy.None } section { @@ -91,12 +88,12 @@ ListView { Layout.fillWidth: false Layout.preferredWidth: 32 * DefaultStyle.dp Layout.minimumWidth: 32 * DefaultStyle.dp - height: 51 * DefaultStyle.dp - visible: !previousDateString || previousDateString != dateString + Layout.preferredHeight: 51 * DefaultStyle.dp + visible: previousDateString.length == 0 || previousDateString != dateString spacing: 0 - //anchors.leftMargin: 45 * DefaultStyle.dp Text { Layout.preferredHeight: 19 * DefaultStyle.dp + Layout.alignment: Qt.AlignCenter text: day.substring(0,3) + '.' color: DefaultStyle.main2_500main wrapMode: Text.NoWrap @@ -109,8 +106,8 @@ ListView { } Rectangle { id: dayNum - Layout.fillWidth: true - Layout.preferredHeight: width + Layout.preferredWidth: 32 * DefaultStyle.dp + Layout.preferredHeight: 32 * DefaultStyle.dp Layout.alignment: Qt.AlignCenter radius: height/2 property var isCurrentDay: UtilsCpp.isCurrentDay(dateTime) @@ -118,9 +115,9 @@ ListView { color: isCurrentDay ? DefaultStyle.main1_500_main : "transparent" Text { - id: dayNumText anchors.centerIn: parent verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter text: UtilsCpp.toDateDayString(dateTime) color: dayNum.isCurrentDay ? DefaultStyle.grey_0 : DefaultStyle.main2_500main wrapMode: Text.NoWrap @@ -208,18 +205,16 @@ ListView { } } - - - MouseArea { - id: confArea - hoverEnabled: mainItem.hoverEnabled - visible: !dateDay.visible && itemDelegate.haveModel - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - mainItem.currentIndex = index - mainItem.conferenceSelected($modelData) - } - } + // MouseArea { + // id: confArea + // hoverEnabled: mainItem.hoverEnabled + // visible: !dateDay.visible && itemDelegate.haveModel + // anchors.fill: parent + // cursorShape: Qt.PointingHandCursor + // onClicked: { + // mainItem.currentIndex = index + // mainItem.conferenceSelected($modelData) + // } + // } } } diff --git a/Linphone/view/Item/Meeting/MeetingSetUp.qml b/Linphone/view/Item/Meeting/MeetingSetUp.qml index a73d81bd3..ea1e0db8b 100644 --- a/Linphone/view/Item/Meeting/MeetingSetUp.qml +++ b/Linphone/view/Item/Meeting/MeetingSetUp.qml @@ -5,7 +5,6 @@ import QtQuick.Controls as Control import Linphone import UtilsCpp 1.0 -//TODO : spacing layout ColumnLayout { id: mainItem spacing: 8 * DefaultStyle.dp @@ -18,7 +17,6 @@ ColumnLayout { Connections { target: mainItem.conferenceInfoGui.core onSchedulerStateChanged: { - console.log("scheduler state changed", mainItem.conferenceInfoGui.core.schedulerState) if (mainItem.conferenceInfoGui.core.schedulerState == LinphoneEnums.ConferenceSchedulerState.Ready) { mainItem.saveSucceed(isCreation) } diff --git a/Linphone/view/Item/VerticalTabBar.qml b/Linphone/view/Item/VerticalTabBar.qml index 2f40a76ec..0870350a1 100644 --- a/Linphone/view/Item/VerticalTabBar.qml +++ b/Linphone/view/Item/VerticalTabBar.qml @@ -7,8 +7,8 @@ import Linphone Control.TabBar { id: mainItem - spacing: 15 * DefaultStyle.dp - topPadding: 20 * DefaultStyle.dp + spacing: 32 * DefaultStyle.dp + topPadding: 36 * DefaultStyle.dp property var model readonly property alias cornerRadius: bottomLeftCorner.radius diff --git a/Linphone/view/Layout/Call/CallLayout.qml b/Linphone/view/Layout/Call/CallLayout.qml index 0597c059c..6d63570ec 100644 --- a/Linphone/view/Layout/Call/CallLayout.qml +++ b/Linphone/view/Layout/Call/CallLayout.qml @@ -68,17 +68,17 @@ Item { Component{ id: activeSpeakerComponent ActiveSpeakerLayout{ - Layout.Layout.fillWidth: true - Layout.Layout.fillHeight: true - call: mainItem.call + Layout.Layout.fillWidth: true + Layout.Layout.fillHeight: true + call: mainItem.call } } Component{ id: gridComponent GridLayout{ - Layout.Layout.fillWidth: true - Layout.Layout.fillHeight: true - call: mainItem.call + Layout.Layout.fillWidth: true + Layout.Layout.fillHeight: true + call: mainItem.call } } } diff --git a/Linphone/view/Layout/Contact/ContactLayout.qml b/Linphone/view/Layout/Contact/ContactLayout.qml index e212e68bc..b93b6d38c 100644 --- a/Linphone/view/Layout/Contact/ContactLayout.qml +++ b/Linphone/view/Layout/Contact/ContactLayout.qml @@ -55,7 +55,6 @@ ColumnLayout { // Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter Avatar { - // TODO : find friend and pass contact argument id: detailAvatar anchors.horizontalCenter: parent.horizontalCenter width: 100 * DefaultStyle.dp diff --git a/Linphone/view/Layout/RightPanelLayout.qml b/Linphone/view/Layout/RightPanelLayout.qml new file mode 100644 index 000000000..0524739f3 --- /dev/null +++ b/Linphone/view/Layout/RightPanelLayout.qml @@ -0,0 +1,30 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +Item { + id: mainItem + property color panelColor: DefaultStyle.grey_100 + property alias headerContent: rightPanelHeader.children + property alias content: rightPanelContent.children + + Rectangle { + id: rightPanelHeader + color: DefaultStyle.grey_0 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 57 * DefaultStyle.dp + } + Rectangle { + id: rightPanelContent + color: mainItem.panelColor + anchors.top: rightPanelHeader.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + } +} \ No newline at end of file diff --git a/Linphone/view/Page/Main/AbstractMainPage.qml b/Linphone/view/Page/Main/AbstractMainPage.qml index 7b44509ae..00ef2382d 100644 --- a/Linphone/view/Page/Main/AbstractMainPage.qml +++ b/Linphone/view/Page/Main/AbstractMainPage.qml @@ -18,24 +18,8 @@ Item { property color rightPanelColor: DefaultStyle.grey_100 property alias leftPanelContent: leftPanel.children property alias rightPanelStackView: rightPanelStackView - property alias contactEditionComp: contactEditionComp property alias rightPanel: rightPanel signal noItemButtonPressed() - signal contactEditionClosed() - - function createContact(name, address) { - var friendGui = Qt.createQmlObject('import Linphone - FriendGui{ - }', mainItem) - friendGui.core.givenName = UtilsCpp.getGivenNameFromFullName(name) - friendGui.core.familyName = UtilsCpp.getFamilyNameFromFullName(name) - friendGui.core.defaultAddress = address - rightPanelStackView.push(contactEditionComp, {"contact": friendGui, "title": qsTr("Nouveau contact"), "saveButtonText": qsTr("Créer")}) - } - - function editContact(friendGui) { - rightPanelStackView.push(contactEditionComp, {"contact": friendGui, "title": qsTr("Modifier contact"), "saveButtonText": qsTr("Enregistrer")}) - } // Control.SplitView { // id: splitView @@ -223,24 +207,8 @@ Item { } } - Item { - Control.StackView { - id: rightPanelStackView - anchors.fill: parent - anchors.topMargin: 39 * DefaultStyle.dp - anchors.leftMargin: 39 * DefaultStyle.dp - } - } - // We need this component here as it is used in multiple subPages (Call and Contact pages) - Component { - id: contactEditionComp - ContactEdition { - property string objectName: "contactEdition" - onCloseEdition: { - rightPanelStackView.pop(Control.StackView.Immediate) - mainItem.contactEditionClosed() - } - } + Control.StackView { + id: rightPanelStackView } } } diff --git a/Linphone/view/Page/Main/CallPage.qml b/Linphone/view/Page/Main/CallPage.qml index a1585e018..97a34bb9b 100644 --- a/Linphone/view/Page/Main/CallPage.qml +++ b/Linphone/view/Page/Main/CallPage.qml @@ -22,6 +22,7 @@ AbstractMainPage { property bool isRegistered: account ? account.core.registrationState == LinphoneEnums.RegistrationState.Ok : false property int selectedParticipantsCount signal startGroupCallRequested() + signal createContactRequested(string name, string address) Connections { enabled: confInfoGui @@ -529,7 +530,7 @@ AbstractMainPage { } onClicked: { detailOptions.close() - mainItem.createContact(contactDetail.contactName, contactDetail.contactAddress) + mainItem.createContactRequested(contactDetail.contactName, contactDetail.contactAddress) } } Button { diff --git a/Linphone/view/Page/Main/ContactPage.qml b/Linphone/view/Page/Main/ContactPage.qml index e95b42bfe..8766e2864 100644 --- a/Linphone/view/Page/Main/ContactPage.qml +++ b/Linphone/view/Page/Main/ContactPage.qml @@ -25,25 +25,28 @@ AbstractMainPage { onNoItemButtonPressed: createContact("", "") + function createContact(name, address) { + var friendGui = Qt.createQmlObject('import Linphone + FriendGui{ + }', mainItem) + friendGui.core.givenName = UtilsCpp.getGivenNameFromFullName(name) + friendGui.core.familyName = UtilsCpp.getFamilyNameFromFullName(name) + friendGui.core.defaultAddress = address + rightPanelStackView.push(contactEdition, {"contact": friendGui, "title": qsTr("Nouveau contact"), "saveButtonText": qsTr("Créer")}) + } + + function editContact(friendGui) { + rightPanelStackView.push(contactEdition, {"contact": friendGui, "title": qsTr("Modifier contact"), "saveButtonText": qsTr("Enregistrer")}) + } + // rightPanelStackView.initialItem: contactDetail Binding { mainItem.showDefaultItem: false - when: rightPanelStackView.currentItem && rightPanelStackView.currentItem.objectName == "contactEdition" + when: rightPanelStackView.currentItem restoreMode: Binding.RestoreBinding } - Connections { - target: mainItem - onContactEditionClosed: { - mainItem.forceListsUpdate() - // mainItem.rightPanelStackView.replace(contactDetail, Control.StackView.Immediate) - } - } showDefaultItem: contactList.model.sourceModel.count === 0 - - function goToNewCall() { - listStackView.replace(newCallItem) - } property MagicSearchProxy allFriends: MagicSearchProxy { searchText: searchBar.text.length === 0 ? "*" : searchBar.text @@ -107,6 +110,7 @@ AbstractMainPage { id: searchBar Layout.leftMargin: leftPanel.leftMargin Layout.rightMargin: leftPanel.rightMargin + Layout.topMargin: 18 * DefaultStyle.dp Layout.fillWidth: true placeholderText: qsTr("Rechercher un contact") } @@ -223,6 +227,12 @@ AbstractMainPage { contactMenuVisible: true searchBarText: searchBar.text model: allFriends + Connections { + target: allFriends + onFriendCreated: (index) => { + contactList.currentIndex = index + } + } onSelectedContactChanged: { if (selectedContact) { favoriteList.currentIndex = -1 @@ -592,4 +602,15 @@ AbstractMainPage { } } } + + Component { + id: contactEdition + ContactEdition { + Control.StackView.onActivated: console.log("edit/create contact") + onCloseEdition: { + if (rightPanelStackView.depth <= 1) rightPanelStackView.clear() + else rightPanelStackView.pop(Control.StackView.Immediate) + } + } + } } diff --git a/Linphone/view/Page/Main/MeetingPage.qml b/Linphone/view/Page/Main/MeetingPage.qml index 4a48486e0..18d01785a 100644 --- a/Linphone/view/Page/Main/MeetingPage.qml +++ b/Linphone/view/Page/Main/MeetingPage.qml @@ -16,14 +16,13 @@ AbstractMainPage { property bool leftPanelEnabled: true property ConferenceInfoGui selectedConference property int meetingListCount - signal newConfCreated() signal returnRequested() signal addParticipantsValidated(list selectedParticipants) Component.onCompleted: rightPanelStackView.push(overridenRightPanel, Control.StackView.Immediate) onSelectedConferenceChanged: { overridenRightPanelStackView.clear() - if (selectedConference) { + if (selectedConference && selectedConference.core.haveModel) { if (!overridenRightPanelStackView.currentItem || overridenRightPanelStackView.currentItem != meetingDetail) overridenRightPanelStackView.replace(meetingDetail, Control.StackView.Immediate) } } @@ -136,7 +135,6 @@ AbstractMainPage { mainItem.selectedConference = conferenceList.selectedConference } RowLayout { - visible: leftPanelStackView.currentItem == listLayout enabled: mainItem.leftPanelEnabled Layout.rightMargin: 39 * DefaultStyle.dp spacing: 0 @@ -200,10 +198,6 @@ AbstractMainPage { } Connections { target: mainItem - onNewConfCreated: { - // TODO : manque un connect côté c++ - conferenceList.forceUpdate() - } } Control.ScrollBar.vertical: ScrollBar { id: meetingsScrollbar @@ -222,8 +216,10 @@ AbstractMainPage { Component { id: createConf ColumnLayout { + id: createConfLayout property ConferenceInfoGui conferenceInfoGui property bool isCreation + spacing: 33 * DefaultStyle.dp RowLayout { width: 320 * DefaultStyle.dp @@ -235,6 +231,8 @@ AbstractMainPage { Layout.preferredHeight: 24 * DefaultStyle.dp icon.width: 24 * DefaultStyle.dp icon.height: 24 * DefaultStyle.dp + topPadding: 6 * DefaultStyle.dp + bottomPadding: 6 * DefaultStyle.dp onClicked: { meetingSetup.conferenceInfoGui.core.undo() leftPanelStackView.pop() @@ -250,10 +248,20 @@ AbstractMainPage { Layout.fillWidth: true } Button { + Layout.preferredWidth: 57 * DefaultStyle.dp topPadding: 6 * DefaultStyle.dp bottomPadding: 6 * DefaultStyle.dp - text: qsTr("Créer") - textSize: 13 * DefaultStyle.dp + contentItem: Text { + text: qsTr("Créer") + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + font { + pixelSize: 13 * DefaultStyle.dp + weight: 600 * DefaultStyle.dp + } + color: DefaultStyle.grey_0 + } onClicked: { if (meetingSetup.conferenceInfoGui.core.subject.length === 0) { UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La conférence doit contenir un sujet"), false) @@ -272,8 +280,23 @@ AbstractMainPage { conferenceInfoGui: parent.conferenceInfoGui isCreation: parent.isCreation Layout.rightMargin: 35 * DefaultStyle.dp + Connections { + target: meetingSetup.conferenceInfoGui ? meetingSetup.conferenceInfoGui.core : null + onConferenceSchedulerStateChanged: { + var mainWin = UtilsCpp.getMainWindow() + if (meetingSetup.conferenceInfoGui.core.schedulerState == LinphoneEnums.ConferenceSchedulerState.AllocationPending + || meetingSetup.conferenceInfoGui.core.schedulerState == LinphoneEnums.ConferenceSchedulerState.Updating) { + mainWin.showLoadingPopup(qsTr("Création de la conférence en cours...")) + } else { + if (meetingSetup.conferenceInfoGui.core.schedulerState == LinphoneEnums.ConferenceSchedulerState.Error) { + UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("La création de la conférence a échoué"), false) + } + mainWin.closeLoadingPopup() + } + createConfLayout.enabled = meetingSetup.conferenceInfoGui.core.schedulerState != LinphoneEnums.ConferenceSchedulerState.AllocationPending + } + } onSaveSucceed: { - mainItem.newConfCreated() leftPanelStackView.pop() UtilsCpp.showInformationPopup(qsTr("Nouvelle réunion"), qsTr("Réunion planifiée avec succès"), true) } @@ -364,6 +387,15 @@ AbstractMainPage { overridenRightPanelStackView.pop() } } + Connections { + target: conferenceEdit.conferenceInfoGui ? conferenceEdit.conferenceInfoGui.core : null + onConferenceSchedulerStateChanged: { + var mainWin = UtilsCpp.getMainWindow() + if (conferenceEdit.conferenceInfoGui.core.schedulerState == LinphoneEnums.ConferenceSchedulerState.Error) { + UtilsCpp.showInformationPopup(qsTr("Erreur"), qsTr("L'édition de la conférence a échoué"), false) + } + } + } } } } @@ -382,12 +414,12 @@ AbstractMainPage { Layout.preferredHeight: 24 * DefaultStyle.dp icon.width: 24 * DefaultStyle.dp icon.height: 24 * DefaultStyle.dp - onClicked: mainItem.returnRequested() + onClicked: container.pop() } ColumnLayout { spacing: 3 * DefaultStyle.dp Text { - text: qsTr("Appel de groupe") + text: qsTr("Ajouter des participants") color: DefaultStyle.main1_500_main maximumLineCount: 1 font { @@ -434,6 +466,7 @@ AbstractMainPage { visible: mainItem.selectedConference spacing: 25 * DefaultStyle.dp Section { + Layout.topMargin: 58 * DefaultStyle.dp visible: mainItem.selectedConference content: RowLayout { spacing: 8 * DefaultStyle.dp diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 39f3072cc..e3c6a519e 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -87,4 +87,6 @@ QtObject { property string squaresFour: "image://internal/squares-four.svg" property string handWaving: "image://internal/hand-waving.svg" property string screencast: "image://internal/screencast.svg" + property string videoconference: "image://internal/video-conference.svg" + property string videoconferenceSelected: "image://internal/video-conference-selected.svg" }