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 adae50d04..ea00cbd85 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 9f99c9f7f..7dea98ff7 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 dbf0b294e..f65645928 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 97bd4dc89..7289af162 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 a84c413e0..7cf088092 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 da111f5db..3081d4787 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 d5b0d768e..e1f47d806 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 70d8daa49..001f40d3e 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 74da7525f..c2acf83b7 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 62be09573..7284abb08 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 000000000..18bfe28a1 --- /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 000000000..0ddd809b6 --- /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 fe3b7801e..424410ff7 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 853fce4de..04eeaa0ae 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 9e3eb907c..8b2bfafdd 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 cae2efee5..d5e06b80f 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 515f57059..c1d61c138 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 a02082d8e..bec835320 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 f2564b928..e4f4486cb 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 8e662e2a0..cb17bb011 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 92b9f417d..12a37b178 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 000000000..6b669492c --- /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 6a6bb1a0a..fc8f85217 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 6ab1b82b2..b693f4803 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 4dc177090..ea99dd2cc 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 c88eabc22..b4feed9d7 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 b9062a1a6..09a28253b 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 97cca3370..80ddb34ed 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 65382f902..b341e9644 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 000000000..b4412da39 --- /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 000000000..08db25548 --- /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 e85bf9b87..08d1872dc 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 35ea66cd8..000000000 --- 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 000000000..b0cda556d --- /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 691425bc3..4b22e1bf2 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 000000000..159b3e2d6 --- /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 00217a530..5d9887a23 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 d1197c370..9501c40e3 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 f6ae0805a..17de9d078 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 000000000..fee4c48e2 --- /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 96c705b11..7e6772e2c 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 a92ab04ae..4c92779e6 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 571347f21..8a062b8f4 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" + +}