From 82b5d6a0081b8cfe0f6af51ae1740f0ca4d27f0c Mon Sep 17 00:00:00 2001 From: Gaelle Braud Date: Thu, 18 Jan 2024 11:52:01 +0100 Subject: [PATCH] contact list fixes: generic VariantList FriendModel resetAddresses check null default account address list update on save generic item for white background lists ui fix set photo friend protect friendmodel setters remove main splitview to stick to the mock-up (keeping it commented cause it may be useful to be able to resize the panels) default image avatar fix crash when address not set --- Linphone/core/App.cpp | 2 + Linphone/core/CMakeLists.txt | 2 + .../core/call-history/CallHistoryList.cpp | 8 +- Linphone/core/call/CallCore.hpp | 1 - Linphone/core/friend/FriendCore.cpp | 357 ++++++++--- Linphone/core/friend/FriendCore.hpp | 84 ++- Linphone/core/friend/FriendInitialProxy.cpp | 3 +- Linphone/core/proxy/AbstractListProxy.hpp | 2 +- Linphone/core/search/MagicSearchProxy.cpp | 3 + Linphone/core/search/MagicSearchProxy.hpp | 3 + Linphone/core/variant/VariantList.cpp | 55 ++ Linphone/core/variant/VariantList.hpp | 56 ++ Linphone/data/CMakeLists.txt | 5 + Linphone/model/friend/FriendModel.cpp | 184 +++++- Linphone/model/friend/FriendModel.hpp | 46 +- Linphone/model/tool/ToolModel.cpp | 8 + Linphone/model/tool/ToolModel.hpp | 1 + Linphone/tool/EnumsToString.cpp | 1 - Linphone/tool/Utils.cpp | 29 +- Linphone/tool/Utils.hpp | 3 + Linphone/view/App/CallsWindow.qml | 1 - Linphone/view/App/Layout/ContactLayout.qml | 163 +++++ Linphone/view/App/Layout/MainLayout.qml | 2 +- Linphone/view/CMakeLists.txt | 7 +- Linphone/view/Item/Account/Accounts.qml | 2 +- Linphone/view/Item/Button.qml | 7 + Linphone/view/Item/Call/CallContactsLists.qml | 120 ++-- Linphone/view/Item/Contact/Avatar.qml | 18 +- .../view/Item/Contact/ContactDescription.qml | 6 +- Linphone/view/Item/Contact/ContactEdition.qml | 250 ++++++++ Linphone/view/Item/Contact/ContactsList.qml | 164 +++++ Linphone/view/Item/Contact/Sticker.qml | 9 +- Linphone/view/Item/ContactsList.qml | 140 ----- Linphone/view/Item/IconLabelButton.qml | 39 ++ Linphone/view/Item/NumericPad.qml | 19 +- .../view/Item/RoundedBackgroundControl.qml | 17 + Linphone/view/Item/TextInput.qml | 31 +- Linphone/view/Page/Main/AbstractMainPage.qml | 119 +++- Linphone/view/Page/Main/CallPage.qml | 326 ++++------ Linphone/view/Page/Main/ContactPage.qml | 574 ++++++++++++++++++ Linphone/view/Prototype/AccountsPrototype.qml | 4 +- Linphone/view/Prototype/FriendPrototype.qml | 4 +- Linphone/view/Style/AppIcons.qml | 8 +- 43 files changed, 2341 insertions(+), 542 deletions(-) create mode 100644 Linphone/core/variant/VariantList.cpp create mode 100644 Linphone/core/variant/VariantList.hpp create mode 100644 Linphone/view/App/Layout/ContactLayout.qml create mode 100644 Linphone/view/Item/Contact/ContactEdition.qml create mode 100644 Linphone/view/Item/Contact/ContactsList.qml delete mode 100644 Linphone/view/Item/ContactsList.qml create mode 100644 Linphone/view/Item/IconLabelButton.qml create mode 100644 Linphone/view/Item/RoundedBackgroundControl.qml create mode 100644 Linphone/view/Page/Main/ContactPage.qml diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index adae50d0..ea00cbd8 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -50,6 +50,7 @@ #include "core/phone-number/PhoneNumberProxy.hpp" #include "core/search/MagicSearchProxy.hpp" #include "core/singleapplication/singleapplication.h" +#include "core/variant/VariantList.hpp" #include "model/object/VariantObject.hpp" #include "tool/Constants.hpp" #include "tool/EnumsToString.hpp" @@ -161,6 +162,7 @@ void App::initCppInterfaces() { qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "CallCore", QLatin1String("Uncreatable")); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallProxy"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallHistoryProxy"); + qmlRegisterType(Constants::MainQmlUri, 1, 0, "VariantList"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "CallGui"); qmlRegisterType(Constants::MainQmlUri, 1, 0, "FriendGui"); qmlRegisterUncreatableType(Constants::MainQmlUri, 1, 0, "FriendCore", QLatin1String("Uncreatable")); diff --git a/Linphone/core/CMakeLists.txt b/Linphone/core/CMakeLists.txt index 9f99c9f7..7dea98ff 100644 --- a/Linphone/core/CMakeLists.txt +++ b/Linphone/core/CMakeLists.txt @@ -33,6 +33,8 @@ list(APPEND _LINPHONEAPP_SOURCES core/proxy/ListProxy.cpp core/proxy/Proxy.cpp core/proxy/SortFilterProxy.cpp + + core/variant/VariantList.cpp ) ## Single Application diff --git a/Linphone/core/call-history/CallHistoryList.cpp b/Linphone/core/call-history/CallHistoryList.cpp index dbf0b294..f6564592 100644 --- a/Linphone/core/call-history/CallHistoryList.cpp +++ b/Linphone/core/call-history/CallHistoryList.cpp @@ -61,7 +61,10 @@ void CallHistoryList::setSelf(QSharedPointer me) { // Avoid copy to lambdas QList> *callLogs = new QList>(); mustBeInLinphoneThread(getClassName()); - auto linphoneCallLogs = CoreModel::getInstance()->getCore()->getCallLogs(); + std::list> linphoneCallLogs; + if (auto account = CoreModel::getInstance()->getCore()->getDefaultAccount()) { + linphoneCallLogs = account->getCallLogs(); + } for (auto it : linphoneCallLogs) { auto model = createCallHistoryCore(it); callLogs->push_back(model); @@ -74,7 +77,8 @@ void CallHistoryList::setSelf(QSharedPointer me) { }); }); }); - + mModelConnection->makeConnectToModel(&CoreModel::defaultAccountChanged, + [this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); }); mModelConnection->makeConnectToModel(&CoreModel::callLogUpdated, [this]() { mModelConnection->invokeToCore([this]() { lUpdate(); }); }); lUpdate(); diff --git a/Linphone/core/call/CallCore.hpp b/Linphone/core/call/CallCore.hpp index 97bd4dc8..7289af16 100644 --- a/Linphone/core/call/CallCore.hpp +++ b/Linphone/core/call/CallCore.hpp @@ -122,7 +122,6 @@ signals: void stateChanged(LinphoneEnums::CallState state); void dirChanged(LinphoneEnums::CallDir dir); void lastErrorMessageChanged(); - void peerAddressChanged(); void durationChanged(int duration); void speakerMutedChanged(); void microphoneMutedChanged(); diff --git a/Linphone/core/friend/FriendCore.cpp b/Linphone/core/friend/FriendCore.cpp index a84c413e..7cf08809 100644 --- a/Linphone/core/friend/FriendCore.cpp +++ b/Linphone/core/friend/FriendCore.cpp @@ -20,13 +20,23 @@ #include "FriendCore.hpp" #include "core/App.hpp" -#include "model/object/VariantObject.hpp" +#include "core/proxy/ListProxy.hpp" #include "model/tool/ToolModel.hpp" #include "tool/Utils.hpp" #include "tool/thread/SafeConnection.hpp" DEFINE_ABSTRACT_OBJECT(FriendCore) +const QString addressLabel = FriendCore::tr("Adresse SIP"); +const QString phoneLabel = FriendCore::tr("Téléphone"); + +QVariant createFriendAddressVariant(const QString &label, const QString &address) { + QVariantMap map; + map.insert("label", label); + map.insert("address", address); + return map; +} + QSharedPointer FriendCore::create(const std::shared_ptr &contact) { auto sharedPointer = QSharedPointer(new FriendCore(contact), &QObject::deleteLater); sharedPointer->setSelf(sharedPointer); @@ -43,28 +53,51 @@ FriendCore::FriendCore(const std::shared_ptr &contact) : QObje mConsolidatedPresence = LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()); mPresenceTimestamp = mFriendModel->getPresenceTimestamp(); mPictureUri = Utils::coreStringToAppString(contact->getPhoto()); - auto address = contact->getAddress(); - mAddress = address ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : "NoAddress"; - auto name = contact->getName(); - mName = - name.empty() ? Utils::getDisplayName(mAddress)->getValue().toString() : Utils::coreStringToAppString(name); + auto vcard = contact->getVcard(); + 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( + createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(address->asStringUriOnly()))); + } + mDefaultAddress = + contact->getAddress() ? Utils::coreStringToAppString(contact->getAddress()->asStringUriOnly()) : QString(); + auto phoneNumbers = contact->getPhoneNumbersWithLabel(); + for (auto &phoneNumber : phoneNumbers) { + mPhoneNumberList.append( + createFriendAddressVariant(Utils::coreStringToAppString(phoneNumber->getLabel()), + Utils::coreStringToAppString(phoneNumber->getPhoneNumber()))); + } + mStarred = contact->getStarred(); mIsSaved = true; } else { mIsSaved = false; mStarred = false; } + connect(this, &FriendCore::addressChanged, &FriendCore::allAddressesChanged); + connect(this, &FriendCore::phoneNumberChanged, &FriendCore::allAddressesChanged); } FriendCore::FriendCore(const FriendCore &friendCore) { // Only copy friend values without models for lambda using and avoid concurrencies. - mAddress = friendCore.mAddress; + mAddressList = friendCore.mAddressList; + mPhoneNumberList = friendCore.mPhoneNumberList; + mDefaultAddress = friendCore.mDefaultAddress; + mGivenName = friendCore.mGivenName; + mFamilyName = friendCore.mFamilyName; + mOrganization = friendCore.mOrganization; + mJob = friendCore.mJob; + mPictureUri = friendCore.mPictureUri; mIsSaved = friendCore.mIsSaved; } FriendCore::~FriendCore() { mustBeInMainThread("~" + getClassName()); - emit mFriendModel->removeListener(); + if (mFriendModel) emit mFriendModel->removeListener(); } void FriendCore::setSelf(SafeSharedPointer me) { @@ -84,20 +117,47 @@ void FriendCore::setSelf(QSharedPointer me) { setPresenceTimestamp(presenceTimestamp); }); }); - mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](QString uri) { + mFriendModelConnection->makeConnectToModel(&FriendModel::pictureUriChanged, [this](const QString &uri) { mFriendModelConnection->invokeToCore([this, uri]() { this->onPictureUriChanged(uri); }); }); mFriendModelConnection->makeConnectToModel(&FriendModel::starredChanged, [this](bool starred) { mFriendModelConnection->invokeToCore([this, starred]() { this->onStarredChanged(starred); }); }); + mFriendModelConnection->makeConnectToModel(&FriendModel::givenNameChanged, [this](const QString &name) { + mFriendModelConnection->invokeToCore([this, name]() { setGivenName(name); }); + }); + mFriendModelConnection->makeConnectToModel(&FriendModel::familyNameChanged, [this](const QString &name) { + mFriendModelConnection->invokeToCore([this, name]() { setFamilyName(name); }); + }); + mFriendModelConnection->makeConnectToModel(&FriendModel::organizationChanged, [this](const QString &orga) { + mFriendModelConnection->invokeToCore([this, orga]() { setOrganization(orga); }); + }); + mFriendModelConnection->makeConnectToModel(&FriendModel::jobChanged, [this](const QString &job) { + mFriendModelConnection->invokeToCore([this, job]() { setJob(job); }); + }); + mFriendModelConnection->makeConnectToModel(&FriendModel::addressesChanged, [this]() { + auto numbers = mFriendModel->getAddresses(); + QList addr; + for (auto &num : numbers) { + addr.append( + createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(num->asStringUriOnly()))); + } + mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); }); + }); + mFriendModelConnection->makeConnectToModel(&FriendModel::phoneNumbersChanged, [this]() { + auto numbers = mFriendModel->getPhoneNumbers(); + QList addr; + for (auto &num : numbers) { + addr.append( + createFriendAddressVariant(phoneLabel, Utils::coreStringToAppString(num->getPhoneNumber()))); + } + mFriendModelConnection->invokeToCore([this, addr]() { resetPhoneNumbers(addr); }); + }); mFriendModelConnection->makeConnectToModel( &FriendModel::objectNameChanged, [this](const QString &objectName) { qDebug() << "object name changed" << objectName; }); // From GUI - mFriendModelConnection->makeConnectToCore(&FriendCore::lSetPictureUri, [this](QString uri) { - mFriendModelConnection->invokeToModel([this, uri]() { mFriendModel->setPictureUri(uri); }); - }); mFriendModelConnection->makeConnectToCore(&FriendCore::lSetStarred, [this](bool starred) { mFriendModelConnection->invokeToModel([this, starred]() { mFriendModel->setStarred(starred); }); }); @@ -110,19 +170,67 @@ void FriendCore::setSelf(QSharedPointer me) { } void FriendCore::reset(const FriendCore &contact) { - setAddress(contact.getAddress()); - setName(contact.getName()); + resetAddresses(contact.getAddresses()); + resetPhoneNumbers(contact.getPhoneNumbers()); + setDefaultAddress(contact.getDefaultAddress()); + setGivenName(contact.getGivenName()); + setFamilyName(contact.getFamilyName()); + setOrganization(contact.getOrganization()); + setJob(contact.getJob()); + setPictureUri(contact.getPictureUri()); setIsSaved(mFriendModel != nullptr); } -QString FriendCore::getName() const { - return mName; +QString FriendCore::getDisplayName() const { + return mGivenName + " " + mFamilyName; } -void FriendCore::setName(QString data) { - if (mName != data) { - mName = data; - emit addressChanged(mName); +QString FriendCore::getGivenName() const { + return mGivenName; +} + +void FriendCore::setGivenName(const QString &name) { + if (mGivenName != name) { + mGivenName = name; + emit givenNameChanged(name); + emit displayNameChanged(); + setIsSaved(false); + } +} + +QString FriendCore::getOrganization() const { + return mOrganization; +} + +void FriendCore::setOrganization(const QString &orga) { + if (mOrganization != orga) { + mOrganization = orga; + emit organizationChanged(); + setIsSaved(false); + } +} + +QString FriendCore::getJob() const { + return mJob; +} + +void FriendCore::setJob(const QString &job) { + if (mJob != job) { + mJob = job; + emit jobChanged(); + setIsSaved(false); + } +} + +QString FriendCore::getFamilyName() const { + return mFamilyName; +} + +void FriendCore::setFamilyName(const QString &name) { + if (mFamilyName != name) { + mFamilyName = name; + emit familyNameChanged(name); + emit displayNameChanged(); setIsSaved(false); } } @@ -137,18 +245,91 @@ void FriendCore::onStarredChanged(bool starred) { emit starredChanged(); } -QString FriendCore::getAddress() const { - return mAddress; +QList FriendCore::getPhoneNumbers() const { + return mPhoneNumberList; } -void FriendCore::setAddress(QString address) { - if (mAddress != address) { - mAddress = address; - emit addressChanged(mAddress); +QVariant FriendCore::getPhoneNumberAt(int index) const { + if (index < 0 || index >= mPhoneNumberList.count()) return QVariant(); + return mPhoneNumberList[index]; +} + +void FriendCore::setPhoneNumberAt(int index, const QString &label, const QString &phoneNumber) { + if (index < 0 || index >= mPhoneNumberList.count()) return; + auto map = mPhoneNumberList[index].toMap(); + auto oldLabel = map["label"].toString(); + if (/*oldLabel != label || */ map["address"] != phoneNumber) { + mPhoneNumberList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, phoneNumber)); + emit phoneNumberChanged(); setIsSaved(false); } } +void FriendCore::removePhoneNumber(int index) { + if (index != -1) mPhoneNumberList.remove(index); + emit phoneNumberChanged(); +} + +void FriendCore::appendPhoneNumber(const QString &label, const QString &number) { + mPhoneNumberList.append(createFriendAddressVariant(label, number)); + emit phoneNumberChanged(); +} + +void FriendCore::resetPhoneNumbers(QList newList) { + mPhoneNumberList = newList; + emit phoneNumberChanged(); +} + +QList FriendCore::getAddresses() const { + return mAddressList; +} + +QVariant FriendCore::getAddressAt(int index) const { + if (index < 0 || index >= mAddressList.count()) return QVariant(); + return mAddressList[index]; +} + +void FriendCore::setAddressAt(int index, const QString &label, const QString &address) { + if (index < 0 || index >= mAddressList.count()) return; + auto map = mAddressList[index].toMap(); + auto oldLabel = map["label"].toString(); + if (/*oldLabel != label || */ map["address"] != address) { + mAddressList.replace(index, createFriendAddressVariant(label.isEmpty() ? oldLabel : label, address)); + emit addressChanged(); + setIsSaved(false); + } +} + +void FriendCore::removeAddress(int index) { + if (index != -1) mAddressList.remove(index); + emit addressChanged(); +} + +void FriendCore::appendAddress(const QString &addr) { + mAddressList.append(createFriendAddressVariant(addressLabel, addr)); + emit addressChanged(); +} + +void FriendCore::resetAddresses(QList newList) { + mAddressList = newList; + emit addressChanged(); +} + +QList FriendCore::getAllAddresses() const { + return mAddressList + mPhoneNumberList; +} + +QString FriendCore::getDefaultAddress() const { + return mDefaultAddress; +} + +void FriendCore::setDefaultAddress(const QString &address) { + if (mDefaultAddress != address) { + mDefaultAddress = address; + emit defaultAddressChanged(); + } +} + LinphoneEnums::ConsolidatedPresence FriendCore::getConsolidatedPresence() const { return mConsolidatedPresence; } @@ -177,6 +358,13 @@ QString FriendCore::getPictureUri() const { return mPictureUri; } +void FriendCore::setPictureUri(const QString &uri) { + if (mPictureUri != uri) { + mPictureUri = uri; + emit pictureUriChanged(); + } +} + void FriendCore::onPictureUriChanged(QString uri) { mPictureUri = uri; emit pictureUriChanged(); @@ -192,21 +380,63 @@ void FriendCore::setIsSaved(bool data) { } } -void FriendCore::writeInto(std::shared_ptr contact) const { +void FriendCore::writeIntoModel(std::shared_ptr model) const { mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO); + model->getFriend()->edit(); + // needed to create the vcard if not created yet + model->setName(mGivenName + (mFamilyName.isEmpty() || mGivenName.isEmpty() ? "" : " ") + mFamilyName); auto core = CoreModel::getInstance()->getCore(); - auto newAddress = core->createAddress(Utils::appStringToCoreString(mAddress)); - contact->edit(); - if (newAddress) contact->setAddress(newAddress); - else qDebug() << "Bad address : " << mAddress; - contact->done(); + + std::list> addresses; + for (auto &addr : mAddressList) { + auto friendAddress = addr.toMap(); + auto num = + linphone::Factory::get()->createAddress(Utils::appStringToCoreString(friendAddress["address"].toString())); + addresses.push_back(num); + } + model->resetAddresses(addresses); + + model->setAddress(ToolModel::interpretUrl(mDefaultAddress)); + + std::list> phones; + for (auto &number : mPhoneNumberList) { + auto friendAddress = number.toMap(); + auto num = linphone::Factory::get()->createFriendPhoneNumber( + Utils::appStringToCoreString(friendAddress["address"].toString()), + Utils::appStringToCoreString(friendAddress["label"].toString())); + phones.push_back(num); + } + model->resetPhoneNumbers(phones); + + model->setGivenName(mGivenName); + model->setFamilyName(mFamilyName); + model->setOrganization(mOrganization); + model->setJob(mJob); + model->setPictureUri(mPictureUri); + model->getFriend()->done(); } -void FriendCore::writeFrom(const std::shared_ptr &contact) { +void FriendCore::writeFromModel(const std::shared_ptr &model) { mustBeInLinphoneThread(QString("[") + gClassName + "] " + Q_FUNC_INFO); - auto address = contact->getAddress(); - mAddress = (address ? Utils::coreStringToAppString(address->asString()) : ""); - mName = Utils::coreStringToAppString(contact->getName()); + + QList addresses; + for (auto &addr : model->getAddresses()) { + addresses.append( + createFriendAddressVariant(addressLabel, Utils::coreStringToAppString(addr->asStringUriOnly()))); + } + mAddressList = addresses; + + QList phones; + for (auto &number : model->getPhoneNumbers()) { + phones.append(createFriendAddressVariant(Utils::coreStringToAppString(number->getLabel()), + Utils::coreStringToAppString(number->getPhoneNumber()))); + } + mPhoneNumberList = phones; + mGivenName = model->getGivenName(); + mFamilyName = model->getFamilyName(); + mOrganization = model->getOrganization(); + mJob = model->getJob(); + mPictureUri = model->getPictureUri(); } void FriendCore::remove() { @@ -225,33 +455,40 @@ void FriendCore::save() { // Save Values to model if (mFriendModel) { mFriendModelConnection->invokeToModel([this, thisCopy]() { // Copy values to avoid concurrency - auto contact = mFriendModel->getFriend(); - thisCopy->writeInto(contact); + thisCopy->writeIntoModel(mFriendModel); thisCopy->deleteLater(); mFriendModelConnection->invokeToCore([this]() { saved(); }); + setIsSaved(true); }); } else { mCoreModelConnection->invokeToModel([this, thisCopy]() { - auto linphoneAddr = ToolModel::interpretUrl(mAddress); + std::shared_ptr contact; auto core = CoreModel::getInstance()->getCore(); - auto contact = core->findFriend(linphoneAddr); - auto friendExists = contact != nullptr; + for (auto &addr : mAddressList) { + auto friendAddress = addr.toMap(); + auto linphoneAddr = ToolModel::interpretUrl(friendAddress["address"].toString()); + contact = core->findFriend(linphoneAddr); + if (contact) break; + } if (contact != nullptr) { - thisCopy->writeInto(contact); + auto friendModel = Utils::makeQObject_ptr(contact); + friendModel->setSelf(friendModel); + thisCopy->writeIntoModel(friendModel); thisCopy->deleteLater(); if (mFriendModelConnection) mFriendModelConnection->invokeToCore([this] { saved(); }); else mCoreModelConnection->invokeToCore([this] { saved(); }); } else { auto contact = core->createFriend(); - thisCopy->writeInto(contact); + std::shared_ptr friendModel; + friendModel = Utils::makeQObject_ptr(contact); + friendModel->setSelf(friendModel); + thisCopy->writeIntoModel(friendModel); thisCopy->deleteLater(); bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); if (created) { - mFriendModel = Utils::makeQObject_ptr(contact); - mFriendModel->setSelf(mFriendModel); core->getDefaultFriendList()->updateSubscriptions(); + emit CoreModel::getInstance()->friendAdded(); } - emit CoreModel::getInstance()->friendAdded(); mCoreModelConnection->invokeToCore([this, created]() { if (created) setSelf(mCoreModelConnection->mCore); setIsSaved(created); @@ -259,38 +496,18 @@ void FriendCore::save() { // Save Values to model } }); } - - // if (mFriendModel) { // Update - // } else { // Creation - // mCoreModelConnection->invokeToModel([this, thisCopy]() { - // auto core = CoreModel::getInstance()->getCore(); - // auto contact = core->createFriend(); - // thisCopy->writeInto(contact); - // thisCopy->deleteLater(); - // bool created = (core->getDefaultFriendList()->addFriend(contact) == linphone::FriendList::Status::OK); - // if (created) { - // mFriendModel = Utils::makeQObject_ptr(contact); - // mFriendModel->setSelf(mFriendModel); - // core->getDefaultFriendList()->updateSubscriptions(); - // } - // emit CoreModel::getInstance()->friendAdded(); - // mCoreModelConnection->invokeToCore([this, created]() { - // if (created) setSelf(mCoreModelConnection->mCore); - // setIsSaved(created); - // }); - // }); - // } } void FriendCore::undo() { // Retrieve values from model if (mFriendModel) { mFriendModelConnection->invokeToModel([this]() { FriendCore *contact = new FriendCore(*this); - contact->writeFrom(mFriendModel->getFriend()); - mFriendModelConnection->invokeToCore([this, contact]() { + contact->writeFromModel(mFriendModel); + contact->moveToThread(App::getInstance()->thread()); + mFriendModelConnection->invokeToCore([this, contact]() mutable { this->reset(*contact); contact->deleteLater(); }); }); } -} +} \ No newline at end of file diff --git a/Linphone/core/friend/FriendCore.hpp b/Linphone/core/friend/FriendCore.hpp index da111f5d..3081d478 100644 --- a/Linphone/core/friend/FriendCore.hpp +++ b/Linphone/core/friend/FriendCore.hpp @@ -21,31 +21,43 @@ #ifndef FRIEND_CORE_H_ #define FRIEND_CORE_H_ +// #include "FriendAddressList.hpp" +#include "core/variant/VariantList.hpp" #include "model/friend/FriendModel.hpp" #include "tool/LinphoneEnums.hpp" #include "tool/thread/SafeConnection.hpp" #include "tool/thread/SafeSharedPointer.hpp" +#include + #include +#include #include #include -#include // This object is defferent from usual Core. It set internal data from directly from GUI. // Values are saved on request. // This allow revert feature. class CoreModel; +class FriendCore; class FriendCore : public QObject, public AbstractObject { Q_OBJECT - Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) - Q_PROPERTY(QString address READ getAddress WRITE setAddress NOTIFY addressChanged) + Q_PROPERTY(QList allAdresses READ getAllAddresses NOTIFY allAddressesChanged) + Q_PROPERTY(QList phoneNumbers READ getPhoneNumbers NOTIFY phoneNumberChanged) + Q_PROPERTY(QList addresses READ getAddresses NOTIFY addressChanged) + Q_PROPERTY(QString givenName READ getGivenName WRITE setGivenName NOTIFY givenNameChanged) + Q_PROPERTY(QString familyName READ getFamilyName WRITE setFamilyName NOTIFY familyNameChanged) + Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged) + Q_PROPERTY(QString organization READ getOrganization WRITE setOrganization NOTIFY organizationChanged) + Q_PROPERTY(QString job READ getJob WRITE setJob NOTIFY jobChanged) + Q_PROPERTY(QString defaultAddress READ getDefaultAddress WRITE setDefaultAddress NOTIFY defaultAddressChanged) Q_PROPERTY(QDateTime presenceTimestamp READ getPresenceTimestamp NOTIFY presenceTimestampChanged) Q_PROPERTY(LinphoneEnums::ConsolidatedPresence consolidatedPresence READ getConsolidatedPresence NOTIFY consolidatedPresenceChanged) Q_PROPERTY(bool isSaved READ getIsSaved NOTIFY isSavedChanged) - Q_PROPERTY(QString pictureUri READ getPictureUri WRITE lSetPictureUri NOTIFY pictureUriChanged) + Q_PROPERTY(QString pictureUri READ getPictureUri WRITE setPictureUri NOTIFY pictureUriChanged) Q_PROPERTY(bool starred READ getStarred WRITE lSetStarred NOTIFY starredChanged) public: @@ -58,14 +70,41 @@ public: void setSelf(SafeSharedPointer me); void reset(const FriendCore &contact); - QString getName() const; - void setName(QString data); + QString getDisplayName() const; + + QString getFamilyName() const; + void setFamilyName(const QString &name); + + QString getGivenName() const; + void setGivenName(const QString &name); + + QString getOrganization() const; + void setOrganization(const QString &name); + + QString getJob() const; + void setJob(const QString &name); bool getStarred() const; void onStarredChanged(bool starred); - QString getAddress() const; - void setAddress(QString address); + QList getPhoneNumbers() const; + QVariant getPhoneNumberAt(int index) const; + Q_INVOKABLE void appendPhoneNumber(const QString &label, const QString &number); + Q_INVOKABLE void removePhoneNumber(int index); + Q_INVOKABLE void setPhoneNumberAt(int index, const QString &label, const QString &phoneNumber); + void resetPhoneNumbers(QList newList); + + QList getAddresses() const; + QVariant getAddressAt(int index) const; + Q_INVOKABLE void appendAddress(const QString &addr); + Q_INVOKABLE void removeAddress(int index); + Q_INVOKABLE void setAddressAt(int index, const QString &label, const QString &address); + void resetAddresses(QList newList); + + void setDefaultAddress(const QString &address); + QString getDefaultAddress() const; + + QList getAllAddresses() const; LinphoneEnums::ConsolidatedPresence getConsolidatedPresence() const; void setConsolidatedPresence(LinphoneEnums::ConsolidatedPresence presence); @@ -77,6 +116,7 @@ public: void setIsSaved(bool isSaved); QString getPictureUri() const; + void setPictureUri(const QString &uri); void onPictureUriChanged(QString uri); void onPresenceReceived(LinphoneEnums::ConsolidatedPresence consolidatedPresence, QDateTime presenceTimestamp); @@ -87,30 +127,39 @@ public: signals: void contactUpdated(); - void nameChanged(QString name); + void displayNameChanged(); + void givenNameChanged(const QString &name); + void familyNameChanged(const QString &name); void starredChanged(); - void addressChanged(QString address); + void phoneNumberChanged(); + void addressChanged(); + void organizationChanged(); + void jobChanged(); void consolidatedPresenceChanged(LinphoneEnums::ConsolidatedPresence level); void presenceTimestampChanged(QDateTime presenceTimestamp); - void sipAddressAdded(const QString &sipAddress); - void sipAddressRemoved(const QString &sipAddress); void pictureUriChanged(); void saved(); void isSavedChanged(bool isSaved); void removed(FriendCore *contact); + void defaultAddressChanged(); + void allAddressesChanged(); - void lSetPictureUri(QString pictureUri); void lSetStarred(bool starred); protected: - void writeInto(std::shared_ptr contact) const; - void writeFrom(const std::shared_ptr &contact); + void writeIntoModel(std::shared_ptr model) const; + void writeFromModel(const std::shared_ptr &model); LinphoneEnums::ConsolidatedPresence mConsolidatedPresence = LinphoneEnums::ConsolidatedPresence::Offline; QDateTime mPresenceTimestamp; - QString mName; + QString mGivenName; + QString mFamilyName; + QString mOrganization; + QString mJob; bool mStarred; - QString mAddress; + QList mPhoneNumberList; + QList mAddressList; + QString mDefaultAddress; QString mPictureUri; bool mIsSaved; std::shared_ptr mFriendModel; @@ -119,5 +168,6 @@ protected: DECLARE_ABSTRACT_OBJECT }; + Q_DECLARE_METATYPE(FriendCore *) #endif diff --git a/Linphone/core/friend/FriendInitialProxy.cpp b/Linphone/core/friend/FriendInitialProxy.cpp index d5b0d768..e1f47d80 100644 --- a/Linphone/core/friend/FriendInitialProxy.cpp +++ b/Linphone/core/friend/FriendInitialProxy.cpp @@ -58,7 +58,8 @@ bool FriendInitialProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sour QRegularExpression search(mFilterText, QRegularExpression::CaseInsensitiveOption | QRegularExpression::UseUnicodePropertiesOption); auto friendData = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent)).value(); - show = friendData->getCore()->getName().indexOf(search) == 0; + auto friendCore = friendData->getCore(); + show = friendCore->getGivenName().indexOf(search) == 0 || friendCore->getFamilyName().indexOf(search) == 0; } return show; diff --git a/Linphone/core/proxy/AbstractListProxy.hpp b/Linphone/core/proxy/AbstractListProxy.hpp index 70d8daa4..001f40d3 100644 --- a/Linphone/core/proxy/AbstractListProxy.hpp +++ b/Linphone/core/proxy/AbstractListProxy.hpp @@ -53,7 +53,7 @@ public: } virtual T getAt(const int &index) const { - if (index < 0 || index >= mList.count()) return nullptr; + if (index < 0 || index >= mList.count()) return T(); else return mList[index]; } diff --git a/Linphone/core/search/MagicSearchProxy.cpp b/Linphone/core/search/MagicSearchProxy.cpp index 74da7525..c2acf83b 100644 --- a/Linphone/core/search/MagicSearchProxy.cpp +++ b/Linphone/core/search/MagicSearchProxy.cpp @@ -26,6 +26,9 @@ MagicSearchProxy::MagicSearchProxy(QObject *parent) : SortFilterProxy(parent) { connect(mList.get(), &MagicSearchList::sourceFlagsChanged, this, &MagicSearchProxy::sourceFlagsChanged); connect(mList.get(), &MagicSearchList::aggregationFlagChanged, this, &MagicSearchProxy::aggregationFlagChanged); 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); } diff --git a/Linphone/core/search/MagicSearchProxy.hpp b/Linphone/core/search/MagicSearchProxy.hpp index 62be0957..7284abb0 100644 --- a/Linphone/core/search/MagicSearchProxy.hpp +++ b/Linphone/core/search/MagicSearchProxy.hpp @@ -48,10 +48,13 @@ public: LinphoneEnums::MagicSearchAggregation getAggregationFlag() const; void setAggregationFlag(LinphoneEnums::MagicSearchAggregation flag); + // Q_INVOKABLE forceUpdate(); + signals: void searchTextChanged(); void sourceFlagsChanged(int sourceFlags); void aggregationFlagChanged(LinphoneEnums::MagicSearchAggregation aggregationFlag); + void forceUpdate(); protected: QString mSearchText; diff --git a/Linphone/core/variant/VariantList.cpp b/Linphone/core/variant/VariantList.cpp new file mode 100644 index 00000000..18bfe28a --- /dev/null +++ b/Linphone/core/variant/VariantList.cpp @@ -0,0 +1,55 @@ +// /* +// * Copyright (c) 2010-2024 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 "VariantList.hpp" + +DEFINE_ABSTRACT_OBJECT(VariantList) + +VariantList::VariantList(QObject *parent) { +} + +VariantList::VariantList(QList list, QObject *parent) { + set(list); +} + +VariantList::~VariantList() { +} + +int VariantList::rowCount(const QModelIndex &parent) const { + return mList.count(); +} + +void VariantList::set(QList list) { + beginResetModel(); + mList = list; + endResetModel(); + emit listModelChanged(); +} + +void VariantList::replace(int index, QVariant newValue) { + mList.replace(index, newValue); +} + +QVariant VariantList::data(const QModelIndex &index, int role) const { + int row = index.row(); + if (!index.isValid() || row < 0 || row >= mList.count()) return QVariant(); + if (role == Qt::DisplayRole) return mList[row]; + return QVariant(); +} \ No newline at end of file diff --git a/Linphone/core/variant/VariantList.hpp b/Linphone/core/variant/VariantList.hpp new file mode 100644 index 00000000..0ddd809b --- /dev/null +++ b/Linphone/core/variant/VariantList.hpp @@ -0,0 +1,56 @@ +// /* +// * Copyright (c) 2010-2024 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 . +// */ +// // This object is defferent from usual Core. It set internal data from directly from GUI. +// // Values are saved on request. +// // This allow revert feature. + +#ifndef VARIANT_LIST_H_ +#define VARIANT_LIST_H_ + +#include "core/proxy/AbstractListProxy.hpp" +#include "tool/AbstractObject.hpp" + +// ///////////////////////////// ADDRESS LIST ///////////////////////////// + +class VariantList : public AbstractListProxy, public AbstractObject { + Q_OBJECT + Q_PROPERTY(QList model WRITE set NOTIFY listModelChanged) +public: + VariantList(QObject *parent = Q_NULLPTR); + VariantList(QList list, QObject *parent = Q_NULLPTR); + ~VariantList(); + + void set(QList list); + + void replace(int index, QVariant newValue); + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + void listModelChanged(); + +private: + DECLARE_ABSTRACT_OBJECT +}; +Q_DECLARE_METATYPE(VariantList *) + +#endif diff --git a/Linphone/data/CMakeLists.txt b/Linphone/data/CMakeLists.txt index fe3b7801..424410ff 100644 --- a/Linphone/data/CMakeLists.txt +++ b/Linphone/data/CMakeLists.txt @@ -52,6 +52,7 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/outgoing_call_rejected.svg" "data/image/microphone.svg" "data/image/microphone-slash.svg" + "data/image/camera.svg" "data/image/video-camera.svg" "data/image/video-camera-slash.svg" "data/image/speaker-high.svg" @@ -66,6 +67,10 @@ list(APPEND _LINPHONEAPP_RC_FILES data/assistant/use-app-sip-account.rc "data/image/heart.svg" "data/image/heart-fill.svg" "data/image/record-fill.svg" + "data/image/pencil-simple.svg" + "data/image/share-network.svg" + "data/image/bell-simple.svg" + "data/image/bell-simple-slash.svg" "data/image/media_encryption_zrtp_pq.svg" data/shaders/roundEffect.vert.qsb diff --git a/Linphone/model/friend/FriendModel.cpp b/Linphone/model/friend/FriendModel.cpp index 853fce4d..04eeaa0a 100644 --- a/Linphone/model/friend/FriendModel.cpp +++ b/Linphone/model/friend/FriendModel.cpp @@ -28,10 +28,20 @@ DEFINE_ABSTRACT_OBJECT(FriendModel) -FriendModel::FriendModel(const std::shared_ptr &contact, QObject *parent) +FriendModel::FriendModel(const std::shared_ptr &contact, const QString &name, QObject *parent) : ::Listener(contact, parent) { mustBeInLinphoneThread(getClassName()); -} + connect(this, &FriendModel::addressesChanged, [this] { + if (mMonitor->getAddresses().size() == 0) return; + if (!mMonitor->getAddress()) mMonitor->setAddress(*mMonitor->getAddresses().begin()); + }); + connect(this, &FriendModel::defaultAddressChanged, [this] { + if (mMonitor->getAddresses().size() == 0) return; + if (!mMonitor->getAddress()) mMonitor->setAddress(*mMonitor->getAddresses().begin()); + }); + if (!contact->getName().empty() || !name.isEmpty()) + mMonitor->setName(contact->getName().empty() ? Utils::appStringToCoreString(name) : contact->getName()); +}; FriendModel::~FriendModel() { mustBeInLinphoneThread("~" + getClassName()); @@ -49,14 +59,163 @@ QDateTime FriendModel::getPresenceTimestamp() const { } else return QDateTime(); } -QString FriendModel::getAddress() const { - return Utils::coreStringToAppString(mMonitor->getAddress()->asStringUriOnly()); +void FriendModel::setAddress(const std::shared_ptr &address) { + if (address) { + mMonitor->setAddress(address); + emit defaultAddressChanged(); + } +} + +std::list> FriendModel::getPhoneNumbers() const { + return mMonitor->getPhoneNumbersWithLabel(); +} + +void FriendModel::appendPhoneNumber(const std::shared_ptr &number) { + if (number) { + mMonitor->addPhoneNumberWithLabel(number); + emit phoneNumbersChanged(); + } +} + +void FriendModel::appendPhoneNumbers(const std::list> &numbers) { + for (auto &num : numbers) + if (num) mMonitor->addPhoneNumberWithLabel(num); + emit phoneNumbersChanged(); +} + +void FriendModel::resetPhoneNumbers(const std::list> &numbers) { + for (auto &num : mMonitor->getPhoneNumbers()) + mMonitor->removePhoneNumber(num); + for (auto &num : numbers) + if (num) mMonitor->addPhoneNumberWithLabel(num); + emit phoneNumbersChanged(); +} + +void FriendModel::removePhoneNumber(const QString &number) { + mMonitor->removePhoneNumber(Utils::appStringToCoreString(number)); + emit phoneNumbersChanged(); +} + +void FriendModel::clearPhoneNumbers() { + for (auto &number : mMonitor->getPhoneNumbers()) + mMonitor->removePhoneNumber(number); + emit phoneNumbersChanged(); +} + +std::list> FriendModel::getAddresses() const { + return mMonitor->getAddresses(); +} + +void FriendModel::appendAddress(const std::shared_ptr &addr) { + if (addr) { + mMonitor->addAddress(addr); + emit addressesChanged(); + } +} + +void FriendModel::appendAddresses(const std::list> &addresses) { + for (auto &addr : addresses) + if (addr) mMonitor->addAddress(addr); + emit addressesChanged(); +} + +void FriendModel::resetAddresses(const std::list> &addresses) { + for (auto &addr : mMonitor->getAddresses()) + mMonitor->removeAddress(addr); + for (auto &addr : addresses) + if (addr) mMonitor->addAddress(addr); + emit addressesChanged(); +} + +void FriendModel::removeAddress(const std::shared_ptr &addr) { + if (addr) { + mMonitor->removeAddress(addr); + emit addressesChanged(); + } +} + +void FriendModel::clearAddresses() { + for (auto &addr : mMonitor->getAddresses()) + if (addr) mMonitor->removeAddress(addr); + emit addressesChanged(); } QString FriendModel::getName() const { return Utils::coreStringToAppString(mMonitor->getName()); } +void FriendModel::setName(const QString &name) { + mMonitor->setName(Utils::appStringToCoreString(name)); +} + +QString FriendModel::getGivenName() const { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + return Utils::coreStringToAppString(mMonitor->getVcard()->getGivenName()); +} + +void FriendModel::setGivenName(const QString &name) { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + mMonitor->getVcard()->setGivenName(Utils::appStringToCoreString(name)); + emit givenNameChanged(name); +} + +QString FriendModel::getFamilyName() const { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + return Utils::coreStringToAppString(mMonitor->getVcard()->getFamilyName()); +} + +void FriendModel::setFamilyName(const QString &name) { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + mMonitor->getVcard()->setFamilyName(Utils::appStringToCoreString(name)); + emit familyNameChanged(name); +} + +QString FriendModel::getOrganization() const { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + return Utils::coreStringToAppString(mMonitor->getVcard()->getOrganization()); +} + +void FriendModel::setOrganization(const QString &orga) { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + mMonitor->getVcard()->setOrganization(Utils::appStringToCoreString(orga)); + emit organizationChanged(orga); +} + +QString FriendModel::getJob() const { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + return Utils::coreStringToAppString(mMonitor->getVcard()->getJobTitle()); +} + +void FriendModel::setJob(const QString &job) { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + mMonitor->getVcard()->setJobTitle(Utils::appStringToCoreString(job)); + emit jobChanged(job); +} + bool FriendModel::getStarred() const { return mMonitor->getStarred(); } @@ -70,11 +229,17 @@ void FriendModel::onPresenceReceived(const std::shared_ptr &co emit presenceReceived(LinphoneEnums::fromLinphone(contact->getConsolidatedPresence()), getPresenceTimestamp()); } -void FriendModel::setPictureUri(QString uri) { +QString FriendModel::getPictureUri() const { + auto vcard = mMonitor->getVcard(); + if (!vcard) { + mMonitor->createVcard(mMonitor->getName()); + } + return Utils::coreStringToAppString(mMonitor->getVcard()->getPhoto()); +} + +void FriendModel::setPictureUri(const QString &uri) { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); - auto account = std::dynamic_pointer_cast(mMonitor); - auto params = account->getParams()->clone(); - auto oldPictureUri = Utils::coreStringToAppString(params->getPictureUri()); + auto oldPictureUri = Utils::coreStringToAppString(mMonitor->getPhoto()); if (!oldPictureUri.isEmpty()) { QString appPrefix = QStringLiteral("image://%1/").arg(AvatarProvider::ProviderId); if (oldPictureUri.startsWith(appPrefix)) { @@ -83,7 +248,6 @@ void FriendModel::setPictureUri(QString uri) { QFile oldPicture(oldPictureUri); if (!oldPicture.remove()) qWarning() << log().arg("Cannot delete old avatar file at " + oldPictureUri); } - params->setPictureUri(Utils::appStringToCoreString(uri)); - account->setParams(params); + mMonitor->setPhoto(Utils::appStringToCoreString(uri)); emit pictureUriChanged(uri); } diff --git a/Linphone/model/friend/FriendModel.hpp b/Linphone/model/friend/FriendModel.hpp index 9e3eb907..8b2bfafd 100644 --- a/Linphone/model/friend/FriendModel.hpp +++ b/Linphone/model/friend/FriendModel.hpp @@ -34,22 +34,60 @@ class FriendModel : public ::Listener &contact, QObject *parent = nullptr); + FriendModel(const std::shared_ptr &contact, + const QString &name = QString(), + QObject *parent = nullptr); ~FriendModel(); QDateTime getPresenceTimestamp() const; - QString getAddress() const; + std::list> getPhoneNumbers() const; + std::list> getAddresses() const; QString getName() const; + QString getGivenName() const; + QString getFamilyName() const; + QString getOrganization() const; + QString getJob() const; bool getStarred() const; std::shared_ptr getFriend() const; + QString getPictureUri() const; - void setPictureUri(QString uri); +protected: + void setAddress(const std::shared_ptr &address); + void appendPhoneNumber(const std::shared_ptr &number); + void appendPhoneNumbers(const std::list> &numbers); + void resetPhoneNumbers(const std::list> &numbers); + void removePhoneNumber(const QString &number); + void clearPhoneNumbers(); + + void appendAddress(const std::shared_ptr &addr); + void appendAddresses(const std::list> &addresses); + void resetAddresses(const std::list> &addresses); + void removeAddress(const std::shared_ptr &addr); + void clearAddresses(); + + void setName(const QString &name); + void setGivenName(const QString &name); + void setFamilyName(const QString &name); + void setOrganization(const QString &orga); + void setJob(const QString &job); + + void setPictureUri(const QString &uri); void setStarred(bool starred); signals: - void pictureUriChanged(QString uri); + void pictureUriChanged(const QString &uri); void starredChanged(bool starred); + void addressesChanged(); + void defaultAddressChanged(); + void phoneNumbersChanged(); + // void nameChanged(const QString &name); + void givenNameChanged(const QString &name); + void familyNameChanged(const QString &name); + void organizationChanged(const QString &orga); + void jobChanged(const QString &job); private: DECLARE_ABSTRACT_OBJECT diff --git a/Linphone/model/tool/ToolModel.cpp b/Linphone/model/tool/ToolModel.cpp index cae2efee..d5e06b80 100644 --- a/Linphone/model/tool/ToolModel.cpp +++ b/Linphone/model/tool/ToolModel.cpp @@ -48,6 +48,14 @@ std::shared_ptr ToolModel::interpretUrl(const QString &addres return interpretedAddress; } +std::shared_ptr ToolModel::makeLinphoneNumber(const QString &label, + const QString &number) { + auto linphoneNumber = std::make_shared(nullptr); + linphoneNumber->setLabel(Utils::appStringToCoreString(label)); + linphoneNumber->setLabel(Utils::appStringToCoreString(number)); + return linphoneNumber; +} + QString ToolModel::getDisplayName(const std::shared_ptr &address) { QString displayName; if (address) { diff --git a/Linphone/model/tool/ToolModel.hpp b/Linphone/model/tool/ToolModel.hpp index 515f5705..c1d61c13 100644 --- a/Linphone/model/tool/ToolModel.hpp +++ b/Linphone/model/tool/ToolModel.hpp @@ -35,6 +35,7 @@ public: ~ToolModel(); static std::shared_ptr interpretUrl(const QString &address); + static std::shared_ptr makeLinphoneNumber(const QString &label, const QString &number); static QString getDisplayName(const std::shared_ptr &address); static QString getDisplayName(QString address); diff --git a/Linphone/tool/EnumsToString.cpp b/Linphone/tool/EnumsToString.cpp index a02082d8..bec83532 100644 --- a/Linphone/tool/EnumsToString.cpp +++ b/Linphone/tool/EnumsToString.cpp @@ -22,7 +22,6 @@ #include "core/App.hpp" #include "model/call/CallModel.hpp" -#include "model/object/VariantObject.hpp" #include "model/tool/ToolModel.hpp" // ============================================================================= diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index f2564b92..e4f4486c 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -26,6 +26,7 @@ #include "model/object/VariantObject.hpp" #include "model/tool/ToolModel.hpp" #include "tool/providers/AvatarProvider.hpp" +#include #include #include #include @@ -49,6 +50,7 @@ VariantObject *Utils::getDisplayName(const QString &address) { QStringList splitted = address.split(":"); if (splitted.size() > 0 && splitted[0] == "sip") splitted.removeFirst(); VariantObject *data = new VariantObject(splitted.first().split("@").first()); // Scope : GUI + if (!data) return nullptr; data->makeRequest([address]() { QString displayName = ToolModel::getDisplayName(address); return displayName; @@ -57,6 +59,19 @@ VariantObject *Utils::getDisplayName(const QString &address) { return data; } +QString Utils::getGivenNameFromFullName(const QString &fullName) { + if (fullName.isEmpty()) return QString(); + auto nameSplitted = fullName.split(" "); + return nameSplitted[0]; +} + +QString Utils::getFamilyNameFromFullName(const QString &fullName) { + if (fullName.isEmpty()) return QString(); + auto nameSplitted = fullName.split(" "); + nameSplitted.removeFirst(); + return nameSplitted.join(" "); +} + QString Utils::getInitials(const QString &username) { if (username.isEmpty()) return ""; @@ -81,7 +96,7 @@ VariantObject *Utils::createCall(const QString &sipAddress, const QString &prepareTransfertAddress, const QHash &headers) { VariantObject *data = new VariantObject(QVariant()); // Scope : GUI - + if (!data) return nullptr; data->makeRequest([sipAddress, prepareTransfertAddress, headers]() { auto call = ToolModel::createCall(sipAddress, prepareTransfertAddress, headers); if (call) { @@ -90,10 +105,14 @@ VariantObject *Utils::createCall(const QString &sipAddress, auto app = App::getInstance(); auto window = app->getCallsWindow(callGui); smartShowWindow(window); + qDebug() << "Utils : call created" << callGui; // callGui.value()->getCore()->lSetCameraEnabled(true); }); return callGui; - } else return QVariant(); + } else { + qDebug() << "Utils : failed to create call"; + return QVariant(); + } }); data->requestValue(); @@ -131,7 +150,7 @@ QQuickWindow *Utils::getMainWindow() { VariantObject *Utils::haveAccount() { VariantObject *result = new VariantObject(); - + if (!result) return nullptr; // Using connect ensure to have sender() and receiver() alive. result->makeRequest([]() { // Model @@ -287,4 +306,8 @@ QStringList Utils::generateSecurityLettersArray(int arraySize, int correctIndex, int Utils::getRandomIndex(int size) { return QRandomGenerator::global()->bounded(size); +} + +void Utils::copyToClipboard(const QString &text) { + QApplication::clipboard()->setText(text); } \ No newline at end of file diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 8e662e2a..cb17bb01 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -52,6 +52,8 @@ public: } Q_INVOKABLE static VariantObject *getDisplayName(const QString &address); + Q_INVOKABLE static QString getGivenNameFromFullName(const QString &fullName); + Q_INVOKABLE static QString getFamilyNameFromFullName(const QString &fullName); Q_INVOKABLE static QString getInitials(const QString &username); // Support UTF32 Q_INVOKABLE static VariantObject *createCall(const QString &sipAddress, @@ -72,6 +74,7 @@ public: Q_INVOKABLE static QString formatDateElapsedTime(const QDateTime &date); Q_INVOKABLE static QStringList generateSecurityLettersArray(int arraySize, int correctIndex, QString correctCode); Q_INVOKABLE static int getRandomIndex(int size); + Q_INVOKABLE static void copyToClipboard(const QString &text); static QString generateSavedFilename(const QString &from, const QString &to); static inline QString coreStringToAppString(const std::string &str) { diff --git a/Linphone/view/App/CallsWindow.qml b/Linphone/view/App/CallsWindow.qml index 92b9f417..12a37b17 100644 --- a/Linphone/view/App/CallsWindow.qml +++ b/Linphone/view/App/CallsWindow.qml @@ -208,7 +208,6 @@ Window { spacing: 10 * DefaultStyle.dp EffectImage { id: callStatusIcon - fillMode: Image.PreserveAspectFit width: 15 * DefaultStyle.dp height: 15 * DefaultStyle.dp source:(mainWindow.call.core.state === LinphoneEnums.CallState.End diff --git a/Linphone/view/App/Layout/ContactLayout.qml b/Linphone/view/App/Layout/ContactLayout.qml new file mode 100644 index 00000000..6b669492 --- /dev/null +++ b/Linphone/view/App/Layout/ContactLayout.qml @@ -0,0 +1,163 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +ColumnLayout { + id: mainItem + spacing: 30 * DefaultStyle.dp + + property var contact + property string contactAddress: contact && contact.core.defaultAddress || "" + property string contactName: contact && contact.core.displayName || "" + + property bool addressVisible: true + + property alias buttonContent: rightButton.data + property alias detailContent: detailControl.data + + component LabelButton: ColumnLayout { + id: labelButton + property alias image: buttonImg + property alias button: button + property string label + spacing: 8 * DefaultStyle.dp + Button { + id: button + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 56 * DefaultStyle.dp + Layout.preferredHeight: 56 * DefaultStyle.dp + topPadding: 16 * DefaultStyle.dp + bottomPadding: 16 * DefaultStyle.dp + leftPadding: 16 * DefaultStyle.dp + rightPadding: 16 * DefaultStyle.dp + background: Rectangle { + anchors.fill: parent + radius: 40 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + contentItem: Image { + id: buttonImg + source: labelButton.source + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + sourceSize.width: 24 * DefaultStyle.dp + sourceSize.height: 24 * DefaultStyle.dp + } + } + Text { + Layout.alignment: Qt.AlignHCenter + text: labelButton.label + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + + Item { + Layout.preferredWidth: mainItem.implicitWidth + Layout.preferredHeight: detailAvatar.height + // 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 + height: 100 * DefaultStyle.dp + contact: mainItem.contact || null + address: !contact && mainItem.contactAddress || mainItem.contactName + } + Item { + id: rightButton + anchors.right: parent.right + anchors.verticalCenter: detailAvatar.verticalCenter + anchors.rightMargin: 20 * DefaultStyle.dp + width: 30 * DefaultStyle.dp + height: 30 * DefaultStyle.dp + } + } + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + // Layout.fillWidth: true + Text { + Layout.alignment: Qt.AlignHCenter + text: mainItem.contactName + horizontalAlignment: Text.AlignHCenter + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + Text { + id: contactAddress + visible: mainItem.addressVisible + text: mainItem.contactAddress + horizontalAlignment: Text.AlignHCenter + font { + pixelSize: 12 * DefaultStyle.dp + weight: 300 * DefaultStyle.dp + } + } + Text { + // connection status + } + } + Item { + // spacing: 10 * DefaultStyle.dp + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: mainItem.implicitWidth + Layout.preferredHeight: childrenRect.height + // Layout.fillHeight: true + LabelButton { + anchors.left: parent.left + // width: 24 * DefaultStyle.dp//image.width + // height: image.height + image.source: AppIcons.phone + label: qsTr("Appel") + button.onClicked: { + var addr = mainItem.contact.core.defaultAddress + var addressEnd = "@sip.linphone.org" + if (!addr.endsWith(addressEnd)) addr += addressEnd + UtilsCpp.createCall(addr) + } + } + LabelButton { + anchors.horizontalCenter: parent.horizontalCenter + // Layout.preferredWidth: image.width + // Layout.preferredHeight: image.height + image.source: AppIcons.chatTeardropText + label: qsTr("Message") + button.onClicked: console.debug("[CallPage.qml] TODO : open conversation") + } + LabelButton { + id: videoCall + anchors.right: parent.right + // Layout.preferredWidth: image.width + // Layout.preferredHeight: image.height + image.source: AppIcons.videoCamera + label: qsTr("Appel Video") + button.onClicked: { + var addr = mainItem.contact.core.defaultAddress + var addressEnd = "@sip.linphone.org" + if(!addr.endsWith(addressEnd)) addr += addressEnd + UtilsCpp.createCall(addr) + console.log("[CallPage.qml] TODO : enable video") + } + } + // Item {Layout.fillWidth: true} + + } + ColumnLayout { + id: detailControl + Layout.fillWidth: true + Layout.fillHeight: true + + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 30 * DefaultStyle.dp + } +} \ No newline at end of file diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 6a6bb1a0..fc8f8521 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -160,7 +160,7 @@ Item { CallPage { id: callPage } - //ContactPage{} + ContactPage{} //ConversationPage{} //MeetingPage{} } diff --git a/Linphone/view/CMakeLists.txt b/Linphone/view/CMakeLists.txt index 6ab1b82b..b693f480 100644 --- a/Linphone/view/CMakeLists.txt +++ b/Linphone/view/CMakeLists.txt @@ -2,6 +2,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/App/Main.qml view/App/CallsWindow.qml + view/App/Layout/ContactLayout.qml view/App/Layout/LoginLayout.qml view/App/Layout/MainLayout.qml @@ -18,6 +19,8 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Contact/Avatar.qml view/Item/Contact/Contact.qml view/Item/Contact/ContactDescription.qml + view/Item/Contact/ContactEdition.qml + view/Item/Contact/ContactsList.qml view/Item/Contact/Sticker.qml view/Item/BusyIndicator.qml @@ -25,12 +28,12 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/Carousel.qml view/Item/CheckBox.qml view/Item/ComboBox.qml - view/Item/ContactsList.qml view/Item/DesktopPopup.qml view/Item/Dialog.qml view/Item/DigitInput.qml view/Item/EffectImage.qml view/Item/ErrorText.qml + view/Item/IconLabelButton.qml view/Item/MovableMouseArea.qml view/Item/NumericPad.qml view/Item/PhoneNumberComboBox.qml @@ -39,6 +42,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Item/PopupButton.qml view/Item/RadioButton.qml view/Item/RectangleTest.qml + view/Item/RoundedBackgroundControl.qml view/Item/SearchBar.qml view/Item/TabBar.qml view/Item/Text.qml @@ -60,6 +64,7 @@ list(APPEND _LINPHONEAPP_QML_FILES view/Page/Main/AbstractMainPage.qml view/Page/Main/CallPage.qml + view/Page/Main/ContactPage.qml # Prototypes view/Prototype/PhoneNumberPrototype.qml diff --git a/Linphone/view/Item/Account/Accounts.qml b/Linphone/view/Item/Account/Accounts.qml index 4dc17709..ea99dd2c 100644 --- a/Linphone/view/Item/Account/Accounts.qml +++ b/Linphone/view/Item/Account/Accounts.qml @@ -57,7 +57,7 @@ Item { Layout.fillWidth: true Layout.topMargin: mainItem.spacing Layout.bottomMargin: mainItem.spacing - height: 1 + height: 1 * DefaultStyle.dp color: DefaultStyle.main2_300 } MouseArea{ diff --git a/Linphone/view/Item/Button.qml b/Linphone/view/Item/Button.qml index c88eabc2..b4feed9d 100644 --- a/Linphone/view/Item/Button.qml +++ b/Linphone/view/Item/Button.qml @@ -20,6 +20,13 @@ Control.Button { topPadding: 11 * DefaultStyle.dp bottomPadding: 11 * DefaultStyle.dp + MouseArea { + anchors.fill: parent + hoverEnabled: true + cursorShape: hovered ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.NoButton + } + background: Item { Rectangle { anchors.fill: parent diff --git a/Linphone/view/Item/Call/CallContactsLists.qml b/Linphone/view/Item/Call/CallContactsLists.qml index b9062a1a..09a28253 100644 --- a/Linphone/view/Item/Call/CallContactsLists.qml +++ b/Linphone/view/Item/Call/CallContactsLists.qml @@ -20,7 +20,7 @@ Item { id: startCallPopup property FriendGui contact onContactChanged: { - + console.log("contact changed", contact) } underlineColor: DefaultStyle.main1_500_main anchors.centerIn: parent @@ -53,56 +53,83 @@ Item { onClicked: startCallPopup.close() } } - Repeater { - id: adresses - model: [{label: "SIP", address: startCallPopup.contact ? startCallPopup.contact.core.address : ""} - // {label: "Work", address: "06000000000"}, - // {label: "Personal", address: "060000000"} - ] //account.adresses - Button { - id: channel - // required property int index - leftPadding: 0 - rightPadding: 0 - // topPadding: 0 - bottomPadding: 0 - Layout.fillWidth: true - - background: Item{} - contentItem: ColumnLayout { - RowLayout { - ColumnLayout { - Text { - Layout.leftMargin: 5 * DefaultStyle.dp - Layout.rightMargin: 5 * DefaultStyle.dp - text: modelData.label - font { - pixelSize: 14 * DefaultStyle.dp - weight: 700 * DefaultStyle.dp - } - } - Text { - Layout.leftMargin: 5 * DefaultStyle.dp - Layout.rightMargin: 5 * DefaultStyle.dp - text: modelData.address - font { - pixelSize: 13 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } + component AddressButton: Button { + property int index + property string label + property string address + id: channel + // required property int index + leftPadding: 0 + rightPadding: 0 + // topPadding: 0 + bottomPadding: 0 + Layout.fillWidth: true + + background: Item{} + contentItem: ColumnLayout { + RowLayout { + ColumnLayout { + Text { + Layout.leftMargin: 5 * DefaultStyle.dp + Layout.rightMargin: 5 * DefaultStyle.dp + text: label + // TODO : change this with domain + font { + pixelSize: 14 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp } } - Item { - Layout.fillWidth: true + Text { + Layout.leftMargin: 5 * DefaultStyle.dp + Layout.rightMargin: 5 * DefaultStyle.dp + text: address + font { + pixelSize: 13 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } } } - Rectangle { - visible: index < adresses.count - 1 + Item { Layout.fillWidth: true - Layout.preferredHeight: 1 * DefaultStyle.dp - color: DefaultStyle.main2_200 } } - onClicked: mainItem.callButtonPressed(modelData.address) + Rectangle { + visible: index < selectedContactAddresses.count - 1 + Layout.fillWidth: true + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + } + onClicked: mainItem.callButtonPressed(address) + } + Repeater { + id: selectedContactAddresses + model: VariantList { + model: startCallPopup.contact && startCallPopup.contact.core.addresses || [] + } + // model: startCallPopup.contact ? startCallPopup.contact.core.addresses : "" + // {label: "Work", address: "06000000000"}, + // {label: "Personal", address: "060000000"} + //account.adresses + delegate: AddressButton { + // property int index + // property string label + // property string address + } + } + Repeater { + id: selectedContactPhoneNumbers + model: VariantList { + model: startCallPopup.contact && startCallPopup.contact.core.phoneNumbers || [] + } + // model: startCallPopup.contact ? startCallPopup.contact.core.addresses : "" + // {label: "Work", address: "06000000000"}, + // {label: "Personal", address: "060000000"} + //account.adresses + delegate: AddressButton { + // property int index + // property string label + // property string address } } } @@ -113,7 +140,7 @@ Item { id: contactsScrollbar active: true interactive: true - policy: Control.ScrollBar.AlwaysOn + policy: Control.ScrollBar.AsNeeded // Layout.fillWidth: true anchors.top: parent.top anchors.bottom: parent.bottom @@ -233,9 +260,11 @@ Item { } ContactsList{ Layout.fillWidth: true + contactMenuVisible: false id: contactList searchBarText: searchBar.text onContactSelected: (contact) => { + console.log("contact selected", contact) startCallPopup.contact = contact startCallPopup.open() } @@ -251,6 +280,7 @@ Item { } ContactsList{ contactMenuVisible: false + Layout.fillWidth: true Layout.fillHeight: true initialHeadersVisible: false model: MagicSearchProxy { diff --git a/Linphone/view/Item/Contact/Avatar.qml b/Linphone/view/Item/Contact/Avatar.qml index 97cca337..80ddb34e 100644 --- a/Linphone/view/Item/Contact/Avatar.qml +++ b/Linphone/view/Item/Contact/Avatar.qml @@ -10,7 +10,7 @@ import UtilsCpp // Initials will be displayed if there isn't any avatar. // TODO : get FriendGui from Call. -StackView{ +StackView { id: mainItem property AccountGui account: null property FriendGui contact: null @@ -20,9 +20,10 @@ StackView{ : call ? call.core.peerAddress : contact - ? contact.core.address + ? contact.core.defaultAddress : '' property var displayNameObj: UtilsCpp.getDisplayName(address) + property string displayNameVal: displayNameObj ? displayNameObj.value : "" property bool haveAvatar: (account && account.core.pictureUri ) || (contact && contact.core.pictureUri) @@ -57,7 +58,7 @@ StackView{ id: initials Rectangle { id: initialItem - property string initials: displayNameObj ? UtilsCpp.getInitials(mainItem.displayNameObj.value) : "" + property string initials: UtilsCpp.getInitials(mainItem.displayNameVal) radius: width / 2 color: DefaultStyle.main2_200 height: mainItem.height @@ -74,6 +75,13 @@ StackView{ capitalization: Font.AllUppercase } } + Image { + visible: initialItem.initials.length === 0 + width: mainItem.width/3 + height: width + source: AppIcons.profile + anchors.centerIn: parent + } } } Component{ @@ -92,7 +100,9 @@ StackView{ sourceSize.height: avatarItem.height fillMode: Image.PreserveAspectCrop anchors.centerIn: parent - source: mainItem.account ? mainItem.account.core.pictureUri : mainItem.contact.core.pictureUri + source: mainItem.account && mainItem.account.core.pictureUri + || mainItem.contact && mainItem.contact.core.pictureUri + || "" mipmap: true } ShaderEffect { diff --git a/Linphone/view/Item/Contact/ContactDescription.qml b/Linphone/view/Item/Contact/ContactDescription.qml index 65382f90..b341e964 100644 --- a/Linphone/view/Item/Contact/ContactDescription.qml +++ b/Linphone/view/Item/Contact/ContactDescription.qml @@ -8,9 +8,9 @@ import UtilsCpp ColumnLayout{ id: mainItem property AccountGui account: null - property var displayName: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : '' - property string topText: displayName ? displayName.value : '' - property string bottomText: account ? account.core.identityAddress : '' + property var displayName: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : "" + property string topText: displayName ? displayName.value : "" + property string bottomText: account ? account.core.identityAddress : "" spacing: 0 width: topTextItem.implicitWidth Text { diff --git a/Linphone/view/Item/Contact/ContactEdition.qml b/Linphone/view/Item/Contact/ContactEdition.qml new file mode 100644 index 00000000..b4412da3 --- /dev/null +++ b/Linphone/view/Item/Contact/ContactEdition.qml @@ -0,0 +1,250 @@ +import QtCore +import QtQuick 2.15 +import QtQuick.Controls as Control +import QtQuick.Dialogs +import QtQuick.Effects +import QtQuick.Layouts +import Linphone +import UtilsCpp 1.0 + +ColumnLayout { + id: mainItem + + property FriendGui 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 + } + } + Button { + background: Item{} + anchors.right: parent.right + anchors.rightMargin: 10 * DefaultStyle.dp + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + contentItem: Image { + anchors.fill: parent + source: AppIcons.closeX + } + 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 + 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 = "" + } + } + } + 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 + TextInput { + label: qsTr("Prénom") + initialText: contact.core.givenName + onEditingFinished: contact.core.givenName = text + backgroundColor: DefaultStyle.grey_0 + } + TextInput { + label: qsTr("Nom") + initialText: contact.core.familyName + onEditingFinished: contact.core.familyName = text + backgroundColor: DefaultStyle.grey_0 + } + TextInput { + label: qsTr("Entreprise") + initialText: contact.core.organization + onEditingFinished: contact.core.organization = text + backgroundColor: DefaultStyle.grey_0 + } + TextInput { + label: qsTr("Fonction") + initialText: contact.core.job + onEditingFinished: contact.core.job = text + backgroundColor: DefaultStyle.grey_0 + } + Item{Layout.fillHeight: true} + } + 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: RowLayout { + TextInput { + label: modelData.label + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.setAddressAt(index, text) + } + initialText: modelData.address + backgroundColor: DefaultStyle.grey_0 + } + Button { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + background: Item{} + contentItem: Image { + anchors.fill: parent + source: AppIcons.closeX + } + onClicked: mainItem.contact.core.removeAddress(index) + } + } + } + RowLayout { + TextInput { + label: qsTr("Adresse SIP") + backgroundColor: DefaultStyle.grey_0 + onEditingFinished: { + if (text.length != 0) mainItem.contact.core.appendAddress(text) + setText("") + } + } + 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 { + TextInput { + label: modelData.label + 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{} + contentItem: Image { + anchors.fill: parent + source: AppIcons.closeX + } + onClicked: mainItem.contact.core.removePhoneNumber(index) + } + } + } + RowLayout { + TextInput { + label: qsTr("Phone") + 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 + onClicked: { + mainItem.contact.core.save() + mainItem.closeEdition() + } + } +} diff --git a/Linphone/view/Item/Contact/ContactsList.qml b/Linphone/view/Item/Contact/ContactsList.qml new file mode 100644 index 00000000..08db2554 --- /dev/null +++ b/Linphone/view/Item/Contact/ContactsList.qml @@ -0,0 +1,164 @@ +import QtQuick 2.7 +import QtQuick.Layouts 1.3 + +import Linphone +import UtilsCpp 1.0 + +ListView { + id: mainItem + Layout.preferredHeight: contentHeight + height: contentHeight + visible: count > 0 + + property string searchBarText + + property bool hoverEnabled: true + property bool contactMenuVisible: true + property bool initialHeadersVisible: true + + 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() + + onContactStarredChanged: model.forceUpdate() + + model: MagicSearchProxy { + searchText: searchBarText.length === 0 ? "*" : searchBarText + } + + delegate: Item { + id: itemDelegate + height: 56 * DefaultStyle.dp + width: mainItem.width + property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null + property var previousDisplayName: previousItem ? previousItem.core.displayName : "" + property var displayName: modelData.core.displayName + Connections { + target: modelData.core + onStarredChanged: mainItem.contactStarredChanged() + } + Text { + id: initial + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + Layout.preferredWidth: 20 * DefaultStyle.dp + opacity: (!previousItem || !previousDisplayName.startsWith(displayName[0])) ? 1 : 0 + text: displayName[0] + color: DefaultStyle.main2_400 + font { + pixelSize: 20 * DefaultStyle.dp + weight: 500 * DefaultStyle.dp + capitalization: Font.AllUppercase + } + } + RowLayout { + id: contactDelegate + anchors.left: initial.right + anchors.leftMargin: 10 * DefaultStyle.dp + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + z: 1 + Avatar { + Layout.preferredWidth: 45 * DefaultStyle.dp + Layout.preferredHeight: 45 * DefaultStyle.dp + contact: modelData + } + Text { + text: itemDelegate.displayName + font.pixelSize: 14 * DefaultStyle.dp + font.capitalization: Font.Capitalize + } + Item { + Layout.fillWidth: true + } + } + + PopupButton { + id: friendPopup + z: 1 + hoverEnabled: mainItem.hoverEnabled + visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened) + popup.x: 0 + popup.padding: 10 * DefaultStyle.dp + anchors.right: parent.right + anchors.rightMargin: 5 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + popup.contentItem: ColumnLayout { + Button { + background: Item{} + contentItem: RowLayout { + Image { + source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart + fillMode: Image.PreserveAspectFit + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + } + Text { + text: modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori") + color: DefaultStyle.main2_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + onClicked: { + modelData.core.lSetStarred(!modelData.core.starred) + friendPopup.close() + } + } + Button { + background: Item{} + contentItem: RowLayout { + EffectImage { + source: AppIcons.trashCan + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + colorizationColor: DefaultStyle.danger_500main + } + Text { + text: qsTr("Supprimmer") + color: DefaultStyle.danger_500main + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + onClicked: { + modelData.core.remove() + friendPopup.close() + } + } + } + } + + MouseArea { + id: contactArea + hoverEnabled: mainItem.hoverEnabled + anchors.fill: contactDelegate + height: mainItem.height + Rectangle { + anchors.fill: contactArea + opacity: 0.7 + color: DefaultStyle.main2_100 + visible: contactArea.containsMouse || friendPopup.hovered || mainItem.currentIndex === index + } + onClicked: { + mainItem.currentIndex = index + mainItem.contactSelected(modelData) + } + } + } +} \ No newline at end of file diff --git a/Linphone/view/Item/Contact/Sticker.qml b/Linphone/view/Item/Contact/Sticker.qml index e85bf9b8..08d1872d 100644 --- a/Linphone/view/Item/Contact/Sticker.qml +++ b/Linphone/view/Item/Contact/Sticker.qml @@ -20,7 +20,8 @@ Item { onEnablePersonalCameraChanged: console.log ("enable camera", enablePersonalCamera) property color color: DefaultStyle.grey_600 property int radius: 15 * DefaultStyle.dp - property var peerAddress: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null + property var peerAddressObj: call ? UtilsCpp.getDisplayName(call.core.peerAddress) : null + property string peerAddress: peerAddressObj ? peerAddressObj.value : "" property var identityAddress: account ? UtilsCpp.getDisplayName(account.core.identityAddress) : null Rectangle { @@ -42,7 +43,7 @@ Item { Layout.alignment: Qt.AlignHCenter Layout.topMargin: 15 * DefaultStyle.dp visible: mainItem.call && mainItem.call != undefined - text: mainItem.peerAddress ? mainItem.peerAddress.value : "" + text: mainItem.peerAddress color: DefaultStyle.grey_0 font { pixelSize: 22 * DefaultStyle.dp @@ -99,8 +100,8 @@ Item { anchors.leftMargin: 10 * DefaultStyle.dp anchors.bottomMargin: 10 * DefaultStyle.dp width: txtMeter.width - text: mainItem.call && mainItem.peerAddress - ? mainItem.peerAddress.value + text: mainItem.peerAddress.length != 0 + ? mainItem.peerAddress : mainItem.account && mainItem.identityAddress ? mainItem.identityAddress.value : "" diff --git a/Linphone/view/Item/ContactsList.qml b/Linphone/view/Item/ContactsList.qml deleted file mode 100644 index 35ea66cd..00000000 --- a/Linphone/view/Item/ContactsList.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick 2.7 -import QtQuick.Layouts 1.3 - -import Linphone -import UtilsCpp 1.0 - -ListView { - id: mainItem - Layout.fillWidth: true - Layout.preferredHeight: contentHeight - height: contentHeight - visible: count > 0 - - property string searchBarText - - property bool contactMenuVisible: true - property bool initialHeadersVisible: true - - signal contactSelected(var contact) - - model: MagicSearchProxy { - searchText: searchBarText.length === 0 ? "*" : searchBarText - } - - delegate: RowLayout { - id: itemDelegate - property var previousItem : mainItem.model.count > 0 && index > 0 ? mainItem.model.getAt(index-1) : null - property var previousDisplayName: previousItem ? UtilsCpp.getDisplayName(previousItem.core.address) : null - property string previousDisplayNameText: previousDisplayName ? previousDisplayName.value : "" - property var displayName: UtilsCpp.getDisplayName(modelData.core.address) - property string displayNameText: displayName ? displayName.value : "" - Text { - Layout.preferredWidth: 20 * DefaultStyle.dp - opacity: (!previousItem || !previousDisplayNameText.startsWith(displayNameText[0])) ? 1 : 0 - text: displayNameText[0] - color: DefaultStyle.main2_400 - font { - pixelSize: 20 * DefaultStyle.dp - weight: 500 * DefaultStyle.dp - capitalization: Font.AllUppercase - } - } - Item { - width: mainItem.width - Layout.preferredWidth: mainItem.width - height: 56 * DefaultStyle.dp - RowLayout { - anchors.fill: parent - z: 1 - Avatar { - Layout.preferredWidth: 45 * DefaultStyle.dp - Layout.preferredHeight: 45 * DefaultStyle.dp - contact: modelData - } - Text { - text: itemDelegate.displayNameText - font.pixelSize: 14 * DefaultStyle.dp - font.capitalization: Font.Capitalize - } - Item { - Layout.fillWidth: true - } - PopupButton { - id: friendPopup - hoverEnabled: true - visible: mainItem.contactMenuVisible && (contactArea.containsMouse || hovered || popup.opened) - popup.x: 0 - popup.padding: 10 * DefaultStyle.dp - popup.contentItem: ColumnLayout { - Button { - background: Item{} - contentItem: RowLayout { - Image { - source: modelData.core.starred ? AppIcons.heartFill : AppIcons.heart - fillMode: Image.PreserveAspectFit - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp - } - Text { - text: modelData.core.starred ? qsTr("Enlever des favoris") : qsTr("Mettre en favori") - color: DefaultStyle.main2_500main - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - } - } - onClicked: { - modelData.core.lSetStarred(!modelData.core.starred) - friendPopup.close() - } - } - Button { - background: Item{} - contentItem: RowLayout { - EffectImage { - source: AppIcons.trashCan - width: 24 * DefaultStyle.dp - height: 24 * DefaultStyle.dp - Layout.preferredWidth: 24 * DefaultStyle.dp - Layout.preferredHeight: 24 * DefaultStyle.dp - fillMode: Image.PreserveAspectFit - colorizationColor: DefaultStyle.danger_500main - } - Text { - text: qsTr("Supprimmer") - color: DefaultStyle.danger_500main - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - } - } - onClicked: { - modelData.core.remove() - friendPopup.close() - } - } - } - } - } - MouseArea { - id: contactArea - hoverEnabled: true - anchors.fill: parent - Rectangle { - anchors.fill: contactArea - opacity: 0.1 - color: DefaultStyle.main2_500main - visible: contactArea.containsMouse || friendPopup.hovered - } - onClicked: { - mainItem.contactSelected(modelData) - } - } - } - } -} \ No newline at end of file diff --git a/Linphone/view/Item/IconLabelButton.qml b/Linphone/view/Item/IconLabelButton.qml new file mode 100644 index 00000000..b0cda556 --- /dev/null +++ b/Linphone/view/Item/IconLabelButton.qml @@ -0,0 +1,39 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import Linphone + +MouseArea { + id: mainItem + property string iconSource + property string text + property color color: DefaultStyle.main2_600 + property int iconSize: 17 * DefaultStyle.dp + property int textSize: 14 * DefaultStyle.dp + property int textWeight: 400 * DefaultStyle.dp + hoverEnabled: true + cursorShape: containsMouse ? Qt.PointingHandCursor : Qt.ArrowCursor + width: content.implicitWidth + RowLayout { + id: content + anchors.verticalCenter: parent.verticalCenter + EffectImage { + Layout.preferredWidth: mainItem.iconSize + Layout.preferredHeight: mainItem.iconSize + width: mainItem.iconSize + height: mainItem.iconSize + source: mainItem.iconSource + colorizationColor: mainItem.color + } + Text { + width: implicitWidth + Layout.fillWidth: true + text: mainItem.text + color: mainItem.color + font { + pixelSize: mainItem.textSize + weight: mainItem.textWeight + } + } + } +} diff --git a/Linphone/view/Item/NumericPad.qml b/Linphone/view/Item/NumericPad.qml index 691425bc..4b22e1bf 100644 --- a/Linphone/view/Item/NumericPad.qml +++ b/Linphone/view/Item/NumericPad.qml @@ -24,15 +24,16 @@ Control.Popup { color: DefaultStyle.grey_100 radius: 20 * DefaultStyle.dp } - MultiEffect { - id: effect - anchors.fill: parent - source: numPadBackground - shadowEnabled: true - shadowColor: DefaultStyle.grey_1000 - shadowOpacity: 0.8 - shadowBlur: 1 - } + // MultiEffect { + // id: effect + // anchors.fill: parent + // source: numPadBackground + // shadowEnabled: true + // shadowColor: DefaultStyle.grey_1000 + // shadowOpacity: 0.1 + // shadowVerticalOffset: -200 * DefaultStyle.dp + // shadowBlur: 1 + // } Rectangle { width: parent.width height: parent.height / 2 diff --git a/Linphone/view/Item/RoundedBackgroundControl.qml b/Linphone/view/Item/RoundedBackgroundControl.qml new file mode 100644 index 00000000..159b3e2d --- /dev/null +++ b/Linphone/view/Item/RoundedBackgroundControl.qml @@ -0,0 +1,17 @@ +import QtQuick +import QtQuick.Controls as Control +import Linphone + +Control.Control { + width: 360 * DefaultStyle.dp + property color backgroundColor + topPadding: 10 * DefaultStyle.dp + bottomPadding: 10 * DefaultStyle.dp + leftPadding: 10 * DefaultStyle.dp + rightPadding: 10 * DefaultStyle.dp + background: Rectangle { + anchors.fill: parent + radius: 15 * DefaultStyle.dp + color: mainItem.backgroundColor ? mainItem.backgroundColor : DefaultStyle.grey_0 + } +} \ No newline at end of file diff --git a/Linphone/view/Item/TextInput.qml b/Linphone/view/Item/TextInput.qml index 00217a53..5d9887a2 100644 --- a/Linphone/view/Item/TextInput.qml +++ b/Linphone/view/Item/TextInput.qml @@ -15,6 +15,8 @@ ColumnLayout { property var validator: RegularExpressionValidator{} property bool fillWidth: false property bool enableBackgroundColors: true + property color backgroundColor: DefaultStyle.grey_100 + property color backgroundBorderColor: DefaultStyle.grey_200 property string initialText property bool enableErrorText: false @@ -26,7 +28,11 @@ ColumnLayout { readonly property string text: textField.text readonly property bool hasActiveFocus: textField.activeFocus - Component.onCompleted: setText(initialText) + signal editingFinished() + + Component.onCompleted: { + setText(initialText) + } function setText(text) { textField.text = text @@ -60,14 +66,12 @@ ColumnLayout { Layout.preferredWidth: mainItem.textInputWidth Layout.preferredHeight: 49 * DefaultStyle.dp radius: 79 * DefaultStyle.dp - color: mainItem.enableBackgroundColors ? DefaultStyle.grey_100 : "transparent" - border.color: mainItem.enableBackgroundColors - ? mainItem.errorTextVisible - ? DefaultStyle.danger_500main - : textField.activeFocus - ? DefaultStyle.main1_500_main - : DefaultStyle.grey_200 - : "transparent" + color: mainItem.backgroundColor + border.color: mainItem.errorTextVisible + ? DefaultStyle.danger_500main + : textField.activeFocus + ? DefaultStyle.main1_500_main + : mainItem.backgroundBorderColor Control.TextField { id: textField @@ -94,6 +98,15 @@ ColumnLayout { color: DefaultStyle.main1_500_main width: 2 * DefaultStyle.dp } + onEditingFinished: { + mainItem.editingFinished() + } + + Keys.onPressed: (event)=> { + if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { + textField.focus = false + } + } } Control.Button { id: eyeButton diff --git a/Linphone/view/Page/Main/AbstractMainPage.qml b/Linphone/view/Page/Main/AbstractMainPage.qml index d1197c37..9501c40e 100644 --- a/Linphone/view/Page/Main/AbstractMainPage.qml +++ b/Linphone/view/Page/Main/AbstractMainPage.qml @@ -19,23 +19,124 @@ Item { property bool showDefaultItem: true signal noItemButtonPressed() - Control.SplitView { - id: splitView - anchors.fill: parent + // Control.SplitView { + // id: splitView + // anchors.fill: parent + // anchors.topMargin: 10 * DefaultStyle.dp - handle: Rectangle { - implicitWidth: 8 * DefaultStyle.dp - color: Control.SplitHandle.hovered ? DefaultStyle.grey_200 : DefaultStyle.grey_100 - } + // handle: Rectangle { + // implicitWidth: 8 * DefaultStyle.dp + // color: Control.SplitHandle.hovered ? DefaultStyle.grey_200 : DefaultStyle.grey_100 + // } + // ColumnLayout { + // id: leftPanel + // Control.SplitView.preferredWidth: 350 * DefaultStyle.dp + // Control.SplitView.minimumWidth: 350 * DefaultStyle.dp + // } + // Rectangle { + // id: rightPanel + // clip: true + // color: DefaultStyle.grey_100 + // StackLayout { + // currentIndex: mainItem.showDefaultItem ? 0 : 1 + // anchors.fill: parent + // ColumnLayout { + // id: defaultItem + // Layout.fillWidth: true + // Layout.fillHeight: true + + // RowLayout { + // Layout.fillHeight: true + // Layout.fillWidth: true + // Layout.alignment: Qt.AlignHCenter + // Item { + // Layout.fillWidth: true + // } + // ColumnLayout { + // spacing: 30 * DefaultStyle.dp + // Item { + // Layout.fillHeight: true + // } + // Image { + // Layout.alignment: Qt.AlignHCenter + // source: AppIcons.noItemImage + // Layout.preferredWidth: 359 * DefaultStyle.dp + // Layout.preferredHeight: 314 * DefaultStyle.dp + // fillMode: Image.PreserveAspectFit + // } + // Text { + // text: mainItem.emptyListText + // Layout.alignment: Qt.AlignHCenter + // font { + // pixelSize: 22 * DefaultStyle.dp + // weight: 800 * DefaultStyle.dp + // } + // } + // Button { + // Layout.alignment: Qt.AlignHCenter + // contentItem: RowLayout { + // Layout.alignment: Qt.AlignVCenter + // EffectImage { + // colorizationColor: DefaultStyle.grey_0 + // source: mainItem.newItemIconSource + // width: 24 * DefaultStyle.dp + // height: 24 * DefaultStyle.dp + // fillMode: Image.PreserveAspectFit + // } + // Text { + // text: mainItem.noItemButtonText + // wrapMode: Text.WordWrap + // color: DefaultStyle.grey_0 + // font { + // weight: 600 * DefaultStyle.dp + // pixelSize: 18 * DefaultStyle.dp + // family: DefaultStyle.defaultFont + // } + // } + // } + // onPressed: mainItem.noItemButtonPressed() + // } + // Item { + // Layout.fillHeight: true + // } + // } + // Item { + // Layout.fillWidth: true + // } + // } + + // } + // ColumnLayout { + // id: rightPanelItem + // Layout.fillWidth: true + // Layout.fillHeight: true + // } + // } + // } + // } + + RowLayout { + anchors.fill: parent + anchors.topMargin: 10 * DefaultStyle.dp + spacing: 0 ColumnLayout { id: leftPanel - Control.SplitView.preferredWidth: 350 * DefaultStyle.dp - Control.SplitView.minimumWidth: 350 * DefaultStyle.dp + Layout.preferredWidth: 403 * DefaultStyle.dp + Layout.minimumWidth: 403 * DefaultStyle.dp + Layout.fillHeight: true + Layout.fillWidth: false + } + Rectangle { + Layout.fillHeight: true + Layout.preferredWidth: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 } Rectangle { id: rightPanel clip: true color: DefaultStyle.grey_100 + Layout.fillWidth: true + Layout.fillHeight: true StackLayout { currentIndex: mainItem.showDefaultItem ? 0 : 1 anchors.fill: parent diff --git a/Linphone/view/Page/Main/CallPage.qml b/Linphone/view/Page/Main/CallPage.qml index f6ae0805..17de9d07 100644 --- a/Linphone/view/Page/Main/CallPage.qml +++ b/Linphone/view/Page/Main/CallPage.qml @@ -17,7 +17,7 @@ AbstractMainPage { onNoItemButtonPressed: goToNewCall() - showDefaultItem: listStackView.currentItem.listView ? listStackView.currentItem.listView.count === 0 : true + showDefaultItem: listStackView.currentItem.listView && listStackView.currentItem.listView.count === 0 && listStackView.currentItem.listView.model.sourceModel.count === 0 || false function goToNewCall() { listStackView.push(newCallItem) @@ -51,6 +51,7 @@ AbstractMainPage { id: historyListItem ColumnLayout { + property alias listView: historyListView RowLayout { Layout.fillWidth: true Layout.leftMargin: listStackView.sideMargin @@ -91,7 +92,9 @@ AbstractMainPage { } Connections { target: deleteHistoryPopup - onAccepted: historyListView.model.removeAllEntries() + onAccepted: { + historyListView.model.removeAllEntries() + } } onClicked: { removeHistory.close() @@ -156,6 +159,7 @@ AbstractMainPage { model: CallHistoryProxy{ filterText: searchBar.text } + currentIndex: -1 spacing: 10 * DefaultStyle.dp @@ -205,7 +209,7 @@ AbstractMainPage { anchors.verticalCenter: parent.verticalCenter Text { id: friendAddress - property var remoteAddress: modelData ? UtilsCpp.getDisplayName(modelData.core.remoteAddress) : undefined + property var remoteAddress: modelData ? UtilsCpp.getDisplayName(modelData.core.remoteAddress) : null text: remoteAddress ? remoteAddress.value : "" font { pixelSize: 14 * DefaultStyle.dp @@ -288,18 +292,13 @@ AbstractMainPage { onCurrentIndexChanged: { mainItem.selectedRowHistoryGui = model.getAt(currentIndex) } - onCountChanged: { - mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible - } - onVisibleChanged: { - mainItem.showDefaultItem = historyListView.count === 0 && historyListView.visible if (!visible) currentIndex = -1 + console.log("visible", visible) } Connections { target: mainItem - onShowDefaultItemChanged: mainItem.showDefaultItem = mainItem.showDefaultItem && historyListView.count === 0 && historyListView.visible onListViewUpdated: { historyListView.model.updateView() } @@ -312,7 +311,7 @@ AbstractMainPage { Control.ScrollBar { id: scrollbar active: true - policy: Control.ScrollBar.AlwaysOn + policy: Control.ScrollBar.AsNeeded Layout.fillHeight: true } } @@ -322,10 +321,6 @@ AbstractMainPage { Component { id: newCallItem ColumnLayout { - Control.StackView.onActivating: { - mainItem.showDefaultItem = false - } - Control.StackView.onDeactivating: mainItem.showDefaultItem = true RowLayout { Layout.leftMargin: listStackView.sideMargin Layout.rightMargin: listStackView.sideMargin @@ -350,214 +345,141 @@ AbstractMainPage { Layout.fillWidth: true } } - RowLayout { + CallContactsLists { Layout.fillWidth: true Layout.fillHeight: true - // Layout.maximumWidth: parent.width - CallContactsLists { - Layout.fillWidth: true - Layout.fillHeight: true - // Layout.leftMargin: listStackView.sideMargin - // Layout.rightMargin: listStackView.sideMargin - groupCallVisible: true - searchBarColor: DefaultStyle.grey_100 - - onCallButtonPressed: (address) => { - var addressEnd = "@sip.linphone.org" - if (!address.endsWith(addressEnd)) address += addressEnd - UtilsCpp.createCall(address) - // var window = UtilsCpp.getCallsWindow() - } + // Layout.leftMargin: listStackView.sideMargin + // Layout.rightMargin: listStackView.sideMargin + groupCallVisible: true + searchBarColor: DefaultStyle.grey_100 + + onCallButtonPressed: (address) => { + var addressEnd = "@sip.linphone.org" + if (!address.endsWith(addressEnd)) address += addressEnd + UtilsCpp.createCall(address) + // var window = UtilsCpp.getCallsWindow() } } } } } - rightPanelContent: RowLayout { + rightPanelContent: Control.StackView { + id: rightPanelStackView Layout.fillWidth: true Layout.fillHeight: true - visible: mainItem.selectedRowHistoryGui != undefined - Layout.topMargin: 45 * DefaultStyle.dp - - Item { - Layout.fillWidth: true - Layout.fillHeight: true + initialItem: emptySelection + Connections { + target: mainItem + onSelectedRowHistoryGuiChanged: { + if (mainItem.selectedRowHistoryGui) rightPanelStackView.replace(contactDetailComp, Control.StackView.Immediate) + else rightPanelStackView.replace(emptySelection, Control.StackView.Immediate) + } } - ColumnLayout { - spacing: 30 * DefaultStyle.dp - Layout.fillWidth: true - Layout.fillHeight: true - - Item { - Layout.preferredHeight: detailAvatar.height - Layout.fillWidth: true - Layout.alignment: Qt.AlignHCenter - Avatar { - id: detailAvatar - anchors.centerIn: parent - width: 100 * DefaultStyle.dp - height: 100 * DefaultStyle.dp - address: mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress || "" - } - PopupButton { - id: detailOptions - anchors.left: detailAvatar.right - anchors.leftMargin: 30 * DefaultStyle.dp - anchors.verticalCenter: parent.verticalCenter - popup.x: width - popup.contentItem: ColumnLayout { - Button { - background: Item {} - contentItem: IconLabel { - text: qsTr("Ajouter aux contacts") - iconSource: AppIcons.plusCircle - } - onClicked: { - // console.debug("[CallPage.qml] TODO : add to contact") - var friendGui = Qt.createQmlObject('import Linphone - FriendGui{}', detailAvatar) - friendGui.core.name = contactName.text - friendGui.core.address = contactAddress.text - friendGui.core.save() - } + onCurrentItemChanged: { + } + } + Component{ + id: emptySelection + Item{} + } + Component { + id: contactDetailComp + ContactLayout { + id: contactDetail + visible: mainItem.selectedRowHistoryGui != undefined + property var remoteName: mainItem.selectedRowHistoryGui ? UtilsCpp.getDisplayName(mainItem.selectedRowHistoryGui.core.remoteAddress) : null + contactAddress: mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress || "" + contactName: remoteName ? remoteName.value : "" + anchors.top: rightPanelStackView.top + anchors.bottom: rightPanelStackView.bottom + anchors.topMargin: 45 * DefaultStyle.dp + anchors.bottomMargin: 45 * DefaultStyle.dp + buttonContent: PopupButton { + id: detailOptions + anchors.right: parent.right + anchors.rightMargin: 30 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter + popup.x: width + popup.contentItem: ColumnLayout { + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Ajouter aux contacts") + iconSource: AppIcons.plusCircle } - Button { - background: Item {} - contentItem: IconLabel { - text: qsTr("Copier l'adresse SIP") - iconSource: AppIcons.copy - } - onClicked: console.debug("[CallPage.qml] TODO : copy SIP address") + onClicked: { + // console.debug("[CallPage.qml] TODO : add to contact") + var friendGui = Qt.createQmlObject('import Linphone + FriendGui{ + }', contactDetail) + detailOptions.close() + friendGui.core.givenName = UtilsCpp.getGivenNameFromFullName(contactDetail.contactName) + friendGui.core.familyName = UtilsCpp.getFamilyNameFromFullName(contactDetail.contactName) + friendGui.core.appendAddress(contactDetail.contactAddress) + friendGui.core.defaultAddress = contactDetail.contactAddress + rightPanelStackView.push(editContact, {"contact": friendGui, "title": qsTr("Ajouter contact"), "saveButtonText": qsTr("Créer")}) } - Button { - background: Item {} - contentItem: IconLabel { - text: qsTr("Bloquer") - iconSource: AppIcons.empty - } - onClicked: console.debug("[CallPage.qml] TODO : block user") + } + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Copier l'adresse SIP") + iconSource: AppIcons.copy } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 * DefaultStyle.dp - color: DefaultStyle.main2_400 + onClicked: UtilsCpp.copyToClipboard(mainItem.selectedRowHistoryGui && mainItem.selectedRowHistoryGui.core.remoteAddress) + } + Button { + background: Item {} + enabled: false + contentItem: IconLabel { + text: qsTr("Bloquer") + iconSource: AppIcons.empty } - Button { - background: Item {} - contentItem: IconLabel { - text: qsTr("Supprimer l'historique") - iconSource: AppIcons.trashCan - colorizationColor: DefaultStyle.danger_500main - } - Connections { - target: deleteForUserPopup - onAccepted: detailListView.model.removeEntriesWithFilter() - } - onClicked: { - detailOptions.close() - deleteForUserPopup.open() - } + onClicked: console.debug("[CallPage.qml] TODO : block user") + } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 2 * DefaultStyle.dp + color: DefaultStyle.main2_400 + } + Button { + background: Item {} + contentItem: IconLabel { + text: qsTr("Supprimer l'historique") + iconSource: AppIcons.trashCan + colorizationColor: DefaultStyle.danger_500main + } + Connections { + target: deleteForUserPopup + onAccepted: detailListView.model.removeEntriesWithFilter() + } + onClicked: { + detailOptions.close() + deleteForUserPopup.open() } } } } - ColumnLayout { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Text { - id: contactName - property var remoteAddress: mainItem.selectedRowHistoryGui ? UtilsCpp.getDisplayName(mainItem.selectedRowHistoryGui.core.remoteAddress) : undefined - Layout.alignment: Qt.AlignHCenter - text: remoteAddress ? remoteAddress.value : "" - horizontalAlignment: Text.AlignHCenter - font { - pixelSize: 14 * DefaultStyle.dp - weight: 400 * DefaultStyle.dp - } - } - Text { - id: contactAddress - text: mainItem.selectedRowHistoryGui ? mainItem.selectedRowHistoryGui.core.remoteAddress : "" - horizontalAlignment: Text.AlignHCenter - font { - pixelSize: 12 * DefaultStyle.dp - weight: 300 * DefaultStyle.dp - } - } - Text { - // connection status - } - } - RowLayout { - spacing: 40 * DefaultStyle.dp - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - // Layout.fillHeight: true - Item {Layout.fillWidth: true} - LabelButton { - Layout.preferredWidth: 24 * DefaultStyle.dp//image.width - Layout.preferredHeight: image.height - image.source: AppIcons.phone - label: qsTr("Appel") - button.onClicked: { - var addr = mainItem.selectedRowHistoryGui.core.remoteAddress - var addressEnd = "@sip.linphone.org" - if (!addr.endsWith(addressEnd)) addr += addressEnd - UtilsCpp.createCall(addr) - } - } - LabelButton { - Layout.preferredWidth: image.width - Layout.preferredHeight: image.height - image.source: AppIcons.chatTeardropText - label: qsTr("Message") - button.onClicked: console.debug("[CallPage.qml] TODO : open conversation") - } - LabelButton { - Layout.preferredWidth: image.width - Layout.preferredHeight: image.height - image.source: AppIcons.videoCamera - label: qsTr("Appel Video") - button.onClicked: { - var addr = mainItem.selectedRowHistoryGui.core.remoteAddress - var addressEnd = "@sip.linphone.org" - if(!addr.endsWith(addressEnd)) addr += addressEnd - UtilsCpp.createCall(addr) - console.log("[CallPage.qml] TODO : enable video") - } - } - Item {Layout.fillWidth: true} - - } - - Control.Control { + detailContent: Control.Control { id: detailControl - Layout.preferredWidth: detailListView.width + leftPadding + rightPadding - implicitHeight: 430 * DefaultStyle.dp + topPadding + bottomPadding - - Layout.alignment: Qt.AlignHCenter - Layout.topMargin: 30 * DefaultStyle.dp - - topPadding: 16 * DefaultStyle.dp - bottomPadding: 21 * DefaultStyle.dp - leftPadding: 17 * DefaultStyle.dp - rightPadding: 17 * DefaultStyle.dp + // Layout.fillWidth: true + Layout.fillHeight: true + Layout.preferredWidth: 360 * DefaultStyle.dp background: Rectangle { id: detailListBackground - width: parent.width - height: detailListView.height color: DefaultStyle.grey_0 radius: 15 * DefaultStyle.dp + anchors.fill: detailListView } - ListView { + + contentItem: ListView { id: detailListView - width: 360 * DefaultStyle.dp - height: Math.min(detailControl.implicitHeight, contentHeight) - anchors.centerIn: detailListBackground - anchors.bottomMargin: 21 * DefaultStyle.dp - spacing: 10 * DefaultStyle.dp + width: parent.width + height: Math.min(detailControl.height, contentHeight) + + spacing: 20 * DefaultStyle.dp clip: true onCountChanged: { @@ -570,10 +492,11 @@ AbstractMainPage { delegate: Item { width:detailListView.width height: 56 * DefaultStyle.dp - // anchors.topMargin: 5 * DefaultStyle.dp - // anchors.bottomMargin: 5 * DefaultStyle.dp RowLayout { anchors.fill: parent + anchors.leftMargin: 20 * DefaultStyle.dp + anchors.rightMargin: 20 * DefaultStyle.dp + anchors.verticalCenter: parent.verticalCenter ColumnLayout { Layout.alignment: Qt.AlignVCenter RowLayout { @@ -619,6 +542,7 @@ AbstractMainPage { } } Item { + Layout.fillHeight: true Layout.fillWidth: true } Text { @@ -633,9 +557,11 @@ AbstractMainPage { } } } - Item { - Layout.fillWidth: true - Layout.fillHeight: true + } + Component { + id: editContact + ContactEdition { + onCloseEdition: rightPanelStackView.pop(Control.StackView.Immediate) } } diff --git a/Linphone/view/Page/Main/ContactPage.qml b/Linphone/view/Page/Main/ContactPage.qml new file mode 100644 index 00000000..fee4c48e --- /dev/null +++ b/Linphone/view/Page/Main/ContactPage.qml @@ -0,0 +1,574 @@ +import QtQuick 2.15 +import QtQuick.Effects +import QtQuick.Layouts +import QtQuick.Controls as Control +import Linphone +import UtilsCpp 1.0 + +AbstractMainPage { + id: mainItem + noItemButtonText: qsTr("Ajouter un contact") + emptyListText: qsTr("Aucun contact pour le moment") + newItemIconSource: AppIcons.newCall + + // disable left panel contact list interaction while a contact is being edited + property bool leftPanelEnabled: true + property FriendGui selectedContact + signal forceListsUpdate() + + onNoItemButtonPressed: createNewContact() + + function createNewContact() { + console.debug("[ContactPage]User: create new contact") + var friendGui = Qt.createQmlObject('import Linphone + FriendGui{ + }', contactDetail) + rightPanelStackView.replace(editContact, {"contact": friendGui, "title": qsTr("Nouveau contact"), "saveButtonText": qsTr("Créer")}) + } + + showDefaultItem: contactList.model.sourceModel.count === 0 + + function goToNewCall() { + listStackView.replace(newCallItem) + } + + leftPanelContent: ColumnLayout { + id: leftPanel + Layout.fillWidth: true + Layout.fillHeight: true + property int sideMargin: 25 * DefaultStyle.dp + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + + Text { + text: qsTr("Contacts") + color: DefaultStyle.main2_700 + font.pixelSize: 29 * DefaultStyle.dp + font.weight: 800 * DefaultStyle.dp + } + Item { + Layout.fillWidth: true + } + Control.Button { + + background: Item { + visible: false + } + contentItem: Image { + source: AppIcons.plusCircle + width: 30 * DefaultStyle.dp + sourceSize.width: 30 * DefaultStyle.dp + fillMode: Image.PreserveAspectFit + } + onClicked: { + mainItem.createNewContact() + } + } + } + + ColumnLayout { + Layout.topMargin: 30 * DefaultStyle.dp + Layout.leftMargin: leftPanel.sideMargin + enabled: mainItem.leftPanelEnabled + SearchBar { + id: searchBar + Layout.rightMargin: leftPanel.sideMargin + Layout.fillWidth: true + placeholderText: qsTr("Rechercher un contact") + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + Control.ScrollBar { + id: contactsScrollbar + active: true + interactive: true + policy: Control.ScrollBar.AsNeeded + // Layout.fillWidth: true + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + // Layout.alignment: Qt.AlignRight + // x: mainItem.x + mainItem.width - width + // anchors.left: control.right + } + Control.ScrollView { + id: listLayout + anchors.fill: parent + Layout.leftMargin: leftPanel.sideMargin + Layout.rightMargin: leftPanel.sideMargin + Layout.topMargin: 25 * DefaultStyle.dp + rightPadding: leftPanel.sideMargin + contentWidth: width - leftPanel.sideMargin + contentHeight: content.height + clip: true + Control.ScrollBar.vertical: contactsScrollbar + + ColumnLayout { + id: content + width: parent.width + // anchors.fill: parent + spacing: 15 * DefaultStyle.dp + Text { + text: qsTr("Aucun contact") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + visible: contactList.count === 0 && favoriteList.count === 0 + Layout.alignment: Qt.AlignHCenter + } + ColumnLayout { + visible: favoriteList.count > 0 + RowLayout { + Text { + text: qsTr("Favoris") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Item { + Layout.fillWidth: true + } + Button { + background: Item{} + contentItem: Image { + source: favoriteList.visible ? AppIcons.upArrow : AppIcons.downArrow + } + onClicked: favoriteList.visible = !favoriteList.visible + } + } + ContactsList{ + id: favoriteList + hoverEnabled: mainItem.leftPanelEnabled + Layout.fillWidth: true + onContactStarredChanged: contactList.model.forceUpdate() + Connections { + target: mainItem + onForceListsUpdate: { + contactList.model.forceUpdate() + } + } + model: MagicSearchProxy { + searchText: searchBar.text.length === 0 ? "*" : searchBar.text + sourceFlags: LinphoneEnums.MagicSearchSource.FavoriteFriends + aggregationFlag: LinphoneEnums.MagicSearchAggregation.Friend + } + onSelectedContactChanged: { + if (selectedContact) { + contactList.currentIndex = -1 + } + mainItem.selectedContact = selectedContact + } + } + } + ColumnLayout { + visible: contactList.count > 0 + RowLayout { + Text { + text: qsTr("All contacts") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Item { + Layout.fillWidth: true + } + Button { + background: Item{} + contentItem: Image { + source: contactList.visible ? AppIcons.upArrow : AppIcons.downArrow + } + onClicked: contactList.visible = !contactList.visible + } + } + ContactsList{ + id: contactList + hoverEnabled: mainItem.leftPanelEnabled + Layout.fillWidth: true + searchBarText: searchBar.text + onContactStarredChanged: favoriteList.model.forceUpdate() + Connections { + target: mainItem + onForceListsUpdate: { + contactList.model.forceUpdate() + } + } + onSelectedContactChanged: { + if (selectedContact) { + favoriteList.currentIndex = -1 + } + mainItem.selectedContact = selectedContact + } + } + } + } + } + } + } + } + rightPanelContent: Control.StackView { + id: rightPanelStackView + Layout.fillWidth: true + Layout.fillHeight: true + initialItem: contactDetail + Binding { + mainItem.showDefaultItem: false + when: rightPanelStackView.currentItem.objectName == "contactEdition" + restoreMode: Binding.RestoreBinding + } + } + Component { + id: contactDetail + RowLayout { + visible: mainItem.selectedContact != undefined + Layout.fillWidth: true + Layout.fillHeight: true + Control.StackView.onActivated: + mainItem.leftPanelEnabled = true + Control.StackView.onDeactivated: mainItem.leftPanelEnabled = false + ContactLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.topMargin: 45 * DefaultStyle.dp + Layout.leftMargin: 74 * DefaultStyle.dp + contact: mainItem.selectedContact + Layout.preferredWidth: 360 * DefaultStyle.dp + buttonContent: Button { + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + background: Item{} + contentItem: Image { + anchors.fill: parent + source: AppIcons.pencil + } + onClicked: rightPanelStackView.replace(editContact, Control.StackView.Immediate) + } + detailContent: ColumnLayout { + Layout.fillWidth: false + Layout.preferredWidth: 360 * DefaultStyle.dp + spacing: 32 * DefaultStyle.dp + ColumnLayout { + spacing: 15 * DefaultStyle.dp + Text { + text: qsTr("Informations") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + + RoundedBackgroundControl { + Layout.preferredHeight: Math.min(226 * DefaultStyle.dp, addrList.contentHeight + topPadding + bottomPadding) + height: Math.min(226 * DefaultStyle.dp, addrList.contentHeight) + Layout.fillWidth: true + contentItem: ListView { + id: addrList + width: 360 * DefaultStyle.dp + height: contentHeight + clip: true + model: VariantList { + model: mainItem.selectedContact ? mainItem.selectedContact.core.allAdresses : [] + } + // model: contactDetail.selectedContact && contactDetail.selectedContact.core.addresses + delegate: Item { + width: addrList.width + height: 70 * DefaultStyle.dp + + ColumnLayout { + anchors.fill: parent + anchors.topMargin: 5 * DefaultStyle.dp + RowLayout { + Layout.fillWidth: true + // Layout.fillHeight: true + // Layout.alignment: Qt.AlignVCenter + Layout.topMargin: 10 * DefaultStyle.dp + Layout.bottomMargin: 10 * DefaultStyle.dp + ColumnLayout { + Layout.fillWidth: true + Text { + Layout.fillWidth: true + // TODO change with domain + text: modelData.label + font { + pixelSize: 13 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + Text { + Layout.fillWidth: true + text: modelData.address + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + Item { + Layout.fillWidth: true + } + Button { + background: Item{} + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + contentItem: Image { + anchors.fill: parent + source: AppIcons.phone + width: 24 * DefaultStyle.dp + height: 24 * DefaultStyle.dp + } + onClicked: { + UtilsCpp.createCall(modelData.address) + } + } + } + + Rectangle { + visible: index != addrList.model.count - 1 + Layout.fillWidth: true + Layout.preferredHeight: 1 * DefaultStyle.dp + Layout.rightMargin: 3 * DefaultStyle.dp + Layout.leftMargin: 3 * DefaultStyle.dp + color: DefaultStyle.main2_200 + clip: true + } + } + } + } + } + } + RoundedBackgroundControl { + visible: companyText.text.length != 0 || jobText.text.length != 0 + Layout.fillWidth: true + // Layout.fillHeight: true + + contentItem: ColumnLayout { + // height: 100 * DefaultStyle.dp + RowLayout { + height: 50 * DefaultStyle.dp + Text { + text: qsTr("Company :") + font { + pixelSize: 13 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + Text { + id: companyText + text: mainItem.selectedContact && mainItem.selectedContact.core.organization + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + RowLayout { + height: 50 * DefaultStyle.dp + Text { + text: qsTr("Job :") + font { + pixelSize: 13 * DefaultStyle.dp + weight: 700 * DefaultStyle.dp + } + } + Text { + id: jobText + text: mainItem.selectedContact && mainItem.selectedContact.core.job + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + } + } + ColumnLayout { + visible: false + Text { + text: qsTr("Medias") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + Button { + Rectangle { + anchors.fill: parent + color: DefaultStyle.grey_0 + radius: 15 * DefaultStyle.dp + } + contentItem: RowLayout { + Image { + Layout.preferredWidth: 24 * DefaultStyle.dp + Layout.preferredHeight: 24 * DefaultStyle.dp + source: AppIcons.shareNetwork + } + Text { + text: qsTr("Show media shared") + font { + pixelSize: 14 * DefaultStyle.dp + weight: 400 * DefaultStyle.dp + } + } + } + onClicked: console.debug("TODO : go to shared media") + } + } + } + } + ColumnLayout { + spacing: 10 * DefaultStyle.dp + ColumnLayout { + visible: false + RowLayout { + Text { + text: qsTr("Confiance") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + } + RoundedBackgroundControl { + contentItem: ColumnLayout { + Text { + text: qsTr("Niveau de confiance - Appareils vérifiés") + } + } + } + } + ColumnLayout { + Text { + text: qsTr("Other actions") + font { + pixelSize: 16 * DefaultStyle.dp + weight: 800 * DefaultStyle.dp + } + } + RoundedBackgroundControl { + Layout.preferredWidth: 360 * DefaultStyle.dp + contentItem: ColumnLayout { + width: parent.width + + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.pencil + text: qsTr("Edit") + onClicked: rightPanelStackView.replace(editContact, Control.StackView.Immediate) + } + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: mainItem.selectedContact && mainItem.selectedContact.core.starred ? AppIcons.heartFill : AppIcons.heart + text: mainItem.selectedContact && mainItem.selectedContact.core.starred ? qsTr("Remove from favourites") : qsTr("Add to favourites") + onClicked: if (mainItem.selectedContact) mainItem.selectedContact.core.lSetStarred(!mainItem.selectedContact.core.starred) + } + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.shareNetwork + text: qsTr("Share") + onClicked: console.log("TODO : share contact") + } + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.bellSlash + text: qsTr("Mute") + onClicked: console.log("TODO : mute contact") + } + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.empty + text: qsTr("Block") + onClicked: console.log("TODO : block contact") + } + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 1 * DefaultStyle.dp + color: DefaultStyle.main2_200 + } + IconLabelButton { + Layout.fillWidth: true + Layout.leftMargin: 15 * DefaultStyle.dp + Layout.rightMargin: 15 * DefaultStyle.dp + Layout.preferredHeight: 50 * DefaultStyle.dp + iconSize: 24 * DefaultStyle.dp + iconSource: AppIcons.trashCan + color: DefaultStyle.danger_500main + text: qsTr("Delete this contact") + onClicked: mainItem.selectedContact.core.remove() + } + } + } + } + // TODO : find device by friend + } + } + } + Component { + id: editContact + ContactEdition { + id: contactEdition + property string objectName: "contactEdition" + contact: mainItem.selectedContact + onCloseEdition: { + mainItem.forceListsUpdate() + rightPanelStackView.replace(contactDetail, Control.StackView.Immediate) + } + } + } +} diff --git a/Linphone/view/Prototype/AccountsPrototype.qml b/Linphone/view/Prototype/AccountsPrototype.qml index 96c705b1..7e6772e2 100644 --- a/Linphone/view/Prototype/AccountsPrototype.qml +++ b/Linphone/view/Prototype/AccountsPrototype.qml @@ -38,8 +38,8 @@ ListView{ Text{ // Store the VariantObject and use value on this object. Do not use value in one line because of looping signals. property var displayName: UtilsCpp.getDisplayName($modelData.identityAddress) - text: displayName.value - onTextChanged: console.log('[ProtoAccounts] Async account displayName: ' +$modelData.identityAddress + " => " +text) + text: displayName ? displayName.value : "" + onTextChanged: console.log("[ProtoAccounts] Async account displayName: " +$modelData.identityAddress + " => " +text) } Text{ text: $modelData.registrationState == LinphoneEnums.RegistrationState.Ok diff --git a/Linphone/view/Prototype/FriendPrototype.qml b/Linphone/view/Prototype/FriendPrototype.qml index a92ab04a..4c92779e 100644 --- a/Linphone/view/Prototype/FriendPrototype.qml +++ b/Linphone/view/Prototype/FriendPrototype.qml @@ -18,8 +18,8 @@ Window{ } TextInput{ placeholderText: 'Name' - initialText: contact.core.name - onTextChanged: contact.core.name = text + initialText: contact.core.givenName + onTextChanged: contact.core.givenName = text } TextInput{ placeholderText: 'Address' diff --git a/Linphone/view/Style/AppIcons.qml b/Linphone/view/Style/AppIcons.qml index 571347f2..8a062b8f 100644 --- a/Linphone/view/Style/AppIcons.qml +++ b/Linphone/view/Style/AppIcons.qml @@ -51,6 +51,7 @@ QtObject { property string outgoingCallRejected: "image://internal/outgoing_call_rejected.svg" property string microphone: "image://internal/microphone.svg" property string microphoneSlash: "image://internal/microphone-slash.svg" + property string camera: "image://internal/camera.svg" property string videoCamera: "image://internal/video-camera.svg" property string videoCameraSlash: "image://internal/video-camera-slash.svg" property string speaker: "image://internal/speaker-high.svg" @@ -67,4 +68,9 @@ QtObject { property string heartFill: "image://internal/heart-fill.svg" property string recordFill: "image://internal/record-fill.svg" property string mediaEncryptionZrtpPq: "image://internal/media_encryption_zrtp_pq.svg" -} \ No newline at end of file + property string pencil: "image://internal/pencil-simple.svg" + property string shareNetwork: "image://internal/share-network.svg" + property string bell: "image://internal/bell-simple.svg" + property string bellSlash: "image://internal/bell-simple-slash.svg" + +}