diff --git a/tests/assets/languages/en.ts b/tests/assets/languages/en.ts index c3ee54151..15241168c 100644 --- a/tests/assets/languages/en.ts +++ b/tests/assets/languages/en.ts @@ -142,6 +142,14 @@ avatarChooserTitle + + companiesInput + + + + companies + + Contacts diff --git a/tests/assets/languages/fr.ts b/tests/assets/languages/fr.ts index c89e84a9f..b43aa3256 100644 --- a/tests/assets/languages/fr.ts +++ b/tests/assets/languages/fr.ts @@ -134,6 +134,14 @@ avatarChooserTitle + + companiesInput + + + + companies + + Contacts diff --git a/tests/resources.qrc b/tests/resources.qrc index 7f5c34851..6fb7514eb 100644 --- a/tests/resources.qrc +++ b/tests/resources.qrc @@ -157,6 +157,7 @@ ui/modules/Common/Popup/PopupShadow.qml ui/modules/Common/qmldir ui/modules/Common/SearchBox.qml + ui/modules/Common/SmartConnect.qml ui/modules/Common/Styles/Animations/CaterpillarAnimationStyle.qml ui/modules/Common/Styles/CollapseStyle.qml ui/modules/Common/Styles/DialogStyle.qml diff --git a/tests/src/components/contacts/ContactModel.cpp b/tests/src/components/contacts/ContactModel.cpp index aba2ba51b..50a73adea 100644 --- a/tests/src/components/contacts/ContactModel.cpp +++ b/tests/src/components/contacts/ContactModel.cpp @@ -8,6 +8,7 @@ #include "../../app/AvatarProvider.hpp" #include "../../app/Database.hpp" #include "../../utils.hpp" +#include "../core/CoreManager.hpp" #include "ContactModel.hpp" @@ -34,8 +35,12 @@ void ContactModel::setUsername (const QString &username) { if (username.length() == 0 || username == getUsername()) return; + m_linphone_friend->edit(); + if (!m_linphone_friend->setName(Utils::qStringToLinphoneString(username))) emit contactUpdated(); + + m_linphone_friend->done(); } // ------------------------------------------------------------------- @@ -45,15 +50,16 @@ QString ContactModel::getAvatar () const { list > photos = m_linphone_friend->getVcard()->getBelcard()->getPhotos(); auto it = find_if( - photos.begin(), photos.end(), [](const shared_ptr &photo) { + photos.cbegin(), photos.cend(), [](const shared_ptr &photo) { return !photo->getValue().compare(0, sizeof(VCARD_SCHEME) - 1, VCARD_SCHEME); } ); - // Returns right path. - if (it == photos.end()) + // No path found. + if (it == photos.cend()) return ""; + // Returns right path. return QStringLiteral("image://%1/%2") .arg(AvatarProvider::PROVIDER_ID) .arg(Utils::linphoneStringToQString( @@ -129,8 +135,65 @@ QVariantList ContactModel::getSipAddresses () const { return list; } -void ContactModel::setSipAddresses (const QVariantList &sip_addresses) { - // TODO. +void ContactModel::addSipAddress (const QString &sip_address) { + shared_ptr address = + CoreManager::getInstance()->getCore()->createAddress( + Utils::qStringToLinphoneString(sip_address) + ); + + if (!address) { + qWarning() << QStringLiteral("Unable to add invalid sip address: `%1`.").arg(sip_address); + return; + } + + qInfo() << QStringLiteral("Add new sip address: `%1`.").arg(sip_address); + + m_linphone_friend->edit(); + m_linphone_friend->addAddress(address); + m_linphone_friend->done(); + + emit contactUpdated(); +} + +bool ContactModel::removeSipAddress (const QString &sip_address) { + list > addresses = m_linphone_friend->getAddresses(); + string match = Utils::qStringToLinphoneString(sip_address); + + auto it = find_if( + addresses.cbegin(), addresses.cend(), + [&match](const shared_ptr &address) { + return match == address->asString(); + } + ); + + if (it == addresses.cend()) { + qWarning() << QStringLiteral("Unable to found sip address: `%1`.") + .arg(sip_address); + return false; + } + + if (addresses.size() == 1) { + qWarning() << QStringLiteral("Unable to remove the only existing sip address: `%1`.") + .arg(sip_address); + return false; + } + + qInfo() << QStringLiteral("Remove sip address: `%1`.").arg(sip_address); + + m_linphone_friend->edit(); + m_linphone_friend->removeAddress(*it); + m_linphone_friend->done(); + + emit contactUpdated(); + + return true; +} + +void ContactModel::updateSipAddress (const QString &old_sip_address, const QString &sip_address) { + if (old_sip_address == sip_address || !removeSipAddress(old_sip_address)) + return; + + addSipAddress(sip_address); } // ------------------------------------------------------------------- @@ -144,10 +207,6 @@ QVariantList ContactModel::getCompanies () const { return list; } -void ContactModel::setCompanies (const QVariantList &companies) { - // TODO. -} - // ------------------------------------------------------------------- QVariantList ContactModel::getEmails () const { @@ -159,8 +218,55 @@ QVariantList ContactModel::getEmails () const { return list; } -void ContactModel::setEmails (const QVariantList &emails) { - // TODO. +void ContactModel::addEmail (const QString &email) { + shared_ptr belCard = m_linphone_friend->getVcard()->getBelcard(); + shared_ptr belCardEmail = + belcard::BelCardGeneric::create(); + belCardEmail->setValue(Utils::qStringToLinphoneString(email)); + + qInfo() << QStringLiteral("Add new email: `%1`.").arg(email); + + m_linphone_friend->edit(); + belCard->addEmail(belCardEmail); + m_linphone_friend->done(); + + emit contactUpdated(); +} + +bool ContactModel::removeEmail (const QString &email) { + shared_ptr belCard = m_linphone_friend->getVcard()->getBelcard(); + list > emails = belCard->getEmails(); + string match = Utils::qStringToLinphoneString(email); + + auto it = find_if( + emails.cbegin(), emails.cend(), + [&match](const shared_ptr &email) { + return match == email->getValue(); + } + ); + + if (it == emails.cend()) { + qWarning() << QStringLiteral("Unable to remove email: `%1`.") + .arg(email); + return false; + } + + qInfo() << QStringLiteral("Remove email: `%1`.").arg(email); + + m_linphone_friend->edit(); + belCard->removeEmail(*it); + m_linphone_friend->done(); + + emit contactUpdated(); + + return true; +} + +void ContactModel::updateEmail (const QString &old_email, const QString &email) { + if (old_email == email || !removeEmail(old_email)) + return; + + addEmail(email); } // ------------------------------------------------------------------- @@ -174,20 +280,12 @@ QVariantList ContactModel::getUrls () const { return list; } -void ContactModel::setUrls (const QVariantList &urls) { - // TODO. -} - // ------------------------------------------------------------------- QList ContactModel::getAddresses () const { } -void ContactModel::setAddresses (const QList &addresses) { - -} - // ------------------------------------------------------------------- Presence::PresenceStatus ContactModel::getPresenceStatus () const { diff --git a/tests/src/components/contacts/ContactModel.hpp b/tests/src/components/contacts/ContactModel.hpp index 112c013f4..8a466bf73 100644 --- a/tests/src/components/contacts/ContactModel.hpp +++ b/tests/src/components/contacts/ContactModel.hpp @@ -31,35 +31,30 @@ class ContactModel : public QObject { Q_PROPERTY( QVariantList sipAddresses READ getSipAddresses - WRITE setSipAddresses NOTIFY contactUpdated ); Q_PROPERTY( QVariantList companies READ getCompanies - WRITE setCompanies NOTIFY contactUpdated ); Q_PROPERTY( QVariantList emails READ getEmails - WRITE setEmails NOTIFY contactUpdated ); Q_PROPERTY( QVariantList urls READ getUrls - WRITE setUrls NOTIFY contactUpdated ); Q_PROPERTY( QList addresses READ getAddresses - WRITE setAddresses NOTIFY contactUpdated ); @@ -84,6 +79,15 @@ class ContactModel : public QObject { public: ContactModel (std::shared_ptr linphone_friend); +public slots: + void addSipAddress (const QString &sip_address); + bool removeSipAddress (const QString &sip_address); + void updateSipAddress (const QString &old_sip_address, const QString &sip_address); + + void addEmail (const QString &email); + bool removeEmail (const QString &email); + void updateEmail (const QString &old_email, const QString &email); + signals: void contactUpdated (); @@ -95,13 +99,11 @@ private: void setAvatar (const QString &path); QVariantList getSipAddresses () const; - void setSipAddresses (const QVariantList &sip_addresses); QVariantList getCompanies () const; void setCompanies (const QVariantList &companies); QVariantList getEmails () const; - void setEmails (const QVariantList &emails); QVariantList getUrls () const; void setUrls (const QVariantList &urls); diff --git a/tests/src/components/contacts/ContactsListProxyModel.hpp b/tests/src/components/contacts/ContactsListProxyModel.hpp index 77bf673e8..7832359f6 100644 --- a/tests/src/components/contacts/ContactsListProxyModel.hpp +++ b/tests/src/components/contacts/ContactsListProxyModel.hpp @@ -3,6 +3,7 @@ #include +class ContactModel; class ContactsListModel; // =================================================================== diff --git a/tests/ui/modules/Common/Form/ListForm.qml b/tests/ui/modules/Common/Form/ListForm.qml index d0e9e2524..07f0248d8 100644 --- a/tests/ui/modules/Common/Form/ListForm.qml +++ b/tests/ui/modules/Common/Form/ListForm.qml @@ -14,8 +14,20 @@ RowLayout { property alias title: text.text property var defaultData: [] + signal changed (int id, string default_value, string new_value) + signal removed (int id, string value) + // ----------------------------------------------------------------- + function setData (data) { + var model = values.model + + model.clear() + data.forEach(function (data) { + model.append({ $value: data }) + }) + } + function _addValue (value) { values.model.append({ $value: value }) @@ -26,9 +38,15 @@ RowLayout { function _handleEditionFinished (index, text) { if (text.length === 0) { + var default_value = values.model.get(index).$value values.model.remove(index) + + if (default_value.length !== 0) { + listForm.removed(index, default_value) + } } else { - values.model.set(index, { $value: text }) + var default_value = values.model.get(index).$value + listForm.changed(index, default_value, text) } addButton.enabled = true @@ -148,9 +166,7 @@ RowLayout { return } - defaultData.forEach(function (data) { - model.append({ $value: data }) - }) + setData(defaultData) } } } diff --git a/tests/ui/modules/Common/Form/TransparentTextInput.qml b/tests/ui/modules/Common/Form/TransparentTextInput.qml index b207af6bc..c6aa53be3 100644 --- a/tests/ui/modules/Common/Form/TransparentTextInput.qml +++ b/tests/ui/modules/Common/Form/TransparentTextInput.qml @@ -4,7 +4,7 @@ import Common 1.0 import Common.Styles 1.0 // =================================================================== -// A editable text that become the content of a box on focus. +// A editable text that has a background color on focus. // =================================================================== Item { @@ -35,6 +35,13 @@ Item { var width = textInput.contentWidth + parent.padding * 2 return width < parent.width ? width : parent.width } + + InvertedMouseArea { + anchors.fill: parent + enabled: textInput.activeFocus + + onPressed: textInput.focus = false + } } TextInput { @@ -58,11 +65,5 @@ Item { cursorPosition = 0 parent.editingFinished() } - - InvertedMouseArea { - anchors.fill: parent - enabled: textInput.activeFocus - onPressed: textInput.focus = false - } } } diff --git a/tests/ui/modules/Common/SmartConnect.qml b/tests/ui/modules/Common/SmartConnect.qml new file mode 100644 index 000000000..bd9fa3a4e --- /dev/null +++ b/tests/ui/modules/Common/SmartConnect.qml @@ -0,0 +1,20 @@ +import QtQuick 2.7 + +import Utils 1.0 + +// =================================================================== + +Item { + property bool _connected: false + + function connect (emitter, signalName, handler) { + Utils.assert(!_connected, 'Smart connect is already connected!') + + emitter[signalName].connect(handler) + _connected = true + + Component.onDestruction.connect(function () { + emitter[signalName].disconnect(handler) + }) + } +} diff --git a/tests/ui/modules/Common/qmldir b/tests/ui/modules/Common/qmldir index 3d6a22440..440a38cf3 100644 --- a/tests/ui/modules/Common/qmldir +++ b/tests/ui/modules/Common/qmldir @@ -68,6 +68,9 @@ PopupShadow 1.0 Popup/PopupShadow.qml # SearchBox SearchBox 1.0 SearchBox.qml +# SmartConnect +SmartConnect 1.0 SmartConnect.qml + # Tooltip TooltipArea 1.0 Tooltip/TooltipArea.qml diff --git a/tests/ui/views/App/MainWindow/ContactEdit.qml b/tests/ui/views/App/MainWindow/ContactEdit.qml index 2a56c0e37..f762f25e2 100644 --- a/tests/ui/views/App/MainWindow/ContactEdit.qml +++ b/tests/ui/views/App/MainWindow/ContactEdit.qml @@ -17,9 +17,7 @@ ColumnLayout { property string sipAddress: '' - property var _contact: ContactsListModel.mapSipAddressToContact( - sipAddress - ) || sipAddress + property var _contact // ----------------------------------------------------------------- @@ -37,21 +35,43 @@ ColumnLayout { } function _setAvatar (path) { - if (!path) { - return - } - - if (Utils.isObject(_contact)) { + if (Utils.isObject(_contact) && path) { _contact.avatar = path.match(/^(?:file:\/\/)?(.*)$/)[1] } + } - // TODO: Not registered contact. + function _setUsername (username) { + if (Utils.isObject(_contact)) { + _contact.username = username + + // Update current text with new username. + usernameInput.text = _contact.username + } } // ----------------------------------------------------------------- spacing: 0 + Component.onCompleted: { + var contact = ContactsListModel.mapSipAddressToContact(sipAddress) + + if (contact) { + infoUpdater.connect(contact, 'onContactUpdated', function () { + addresses.setData(contact.sipAddresses) + companies.setData(contact.companies) + emails.setData(contact.emails) + urls.setData(contact.urls) + }) + + _contact = contact + } else { + _contact = sipAddress + } + } + + // ----------------------------------------------------------------- + FileDialog { id: avatarChooser @@ -89,15 +109,14 @@ ColumnLayout { id: avatar anchors.fill: parent - image: _contact.avatar - username: LinphoneUtils.getContactUsername(_contact) + username: LinphoneUtils.getContactUsername(_contact) || 'John Doe' visible: isLoaded() && !parent.hovered } } TransparentTextInput { - id: editUsername + id: usernameInput Layout.fillWidth: true Layout.preferredHeight: ContactEditStyle.infoBar.buttons.size @@ -111,12 +130,7 @@ ColumnLayout { text: avatar.username - onEditingFinished: { - _contact.username = text - - // Update current text with new username. - text = _contact.username - } + onEditingFinished: _setUsername(text) } ActionBar { @@ -157,28 +171,56 @@ ColumnLayout { contentWidth: width - ScrollBar.vertical.width - leftMargin - rightMargin flickableDirection: Flickable.VerticalFlick - leftMargin: 40 - rightMargin: 20 - topMargin: 40 + leftMargin: ContactEditStyle.values.leftMargin + rightMargin: ContactEditStyle.values.rightMargin + topMargin: ContactEditStyle.values.topMargin ColumnLayout { id: infoList width: flick.contentWidth + SmartConnect { + id: infoUpdater + } + ListForm { + id: addresses + defaultData: _contact.sipAddresses placeholder: qsTr('sipAccountsInput') title: qsTr('sipAccounts') + + onChanged: default_value.length === 0 + ? _contact.addSipAddress(new_value) + : _contact.updateSipAddress(default_value, new_value) + onRemoved: _contact.removeSipAddress(value) } ListForm { + id: companies + + defaultData: _contact.companies + placeholder: qsTr('companiesInput') + title: qsTr('companies') + } + + ListForm { + id: emails + defaultData: _contact.emails placeholder: qsTr('emailsInput') title: qsTr('emails') + + onChanged: default_value.length === 0 + ? _contact.addEmail(new_value) + : _contact.updateEmail(default_value, new_value) + onRemoved: _contact.removeEmail(value) } ListForm { + id: urls + defaultData: _contact.urls placeholder: qsTr('webSitesInput') title: qsTr('webSites') diff --git a/tests/ui/views/App/Styles/MainWindow/ContactEditStyle.qml b/tests/ui/views/App/Styles/MainWindow/ContactEditStyle.qml index 3c80d9eea..1f2ce88cc 100644 --- a/tests/ui/views/App/Styles/MainWindow/ContactEditStyle.qml +++ b/tests/ui/views/App/Styles/MainWindow/ContactEditStyle.qml @@ -24,4 +24,10 @@ QtObject { property int fontSize: 13 } } + + property QtObject values: QtObject { + property int leftMargin: 40 + property int rightMargin: 20 + property int topMargin: 40 + } }