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" + +}