diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6e7a44bf..4ff9ebcf7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,7 @@ foreach (package ${QT5_PACKAGES}) endforeach () list(APPEND LIBS "${CMAKE_SOURCE_DIR}/../OUTPUT/desktop/lib64/liblinphone++.so") +list(APPEND LIBS "${CMAKE_SOURCE_DIR}/../OUTPUT/desktop/lib64/libbelcard.so") set(SOURCES src/app/App.cpp diff --git a/tests/assets/images/contact_card_photo_hovered.svg b/tests/assets/images/contact_card_photo_hovered.svg new file mode 100644 index 000000000..e7b9d282d --- /dev/null +++ b/tests/assets/images/contact_card_photo_hovered.svg @@ -0,0 +1,16 @@ + + + + contact_card_photo_over + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/tests/assets/images/contact_card_photo_normal.svg b/tests/assets/images/contact_card_photo_normal.svg new file mode 100644 index 000000000..b4e52a62f --- /dev/null +++ b/tests/assets/images/contact_card_photo_normal.svg @@ -0,0 +1,16 @@ + + + + contact_card_photo_default + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/tests/assets/images/contact_card_photo_pressed.svg b/tests/assets/images/contact_card_photo_pressed.svg new file mode 100644 index 000000000..cb5a9941a --- /dev/null +++ b/tests/assets/images/contact_card_photo_pressed.svg @@ -0,0 +1,16 @@ + + + + contact_card_photo_over + Created with Sketch. + + + + + + + + + + + diff --git a/tests/assets/languages/en.ts b/tests/assets/languages/en.ts index 29797c752..e18fe2523 100644 --- a/tests/assets/languages/en.ts +++ b/tests/assets/languages/en.ts @@ -138,6 +138,10 @@ webSitesInput URL + + avatarChooserTitle + + Contacts diff --git a/tests/assets/languages/fr.ts b/tests/assets/languages/fr.ts index 70069efa0..3291f13a3 100644 --- a/tests/assets/languages/fr.ts +++ b/tests/assets/languages/fr.ts @@ -130,6 +130,10 @@ webSitesInput URL + + avatarChooserTitle + + Contacts diff --git a/tests/resources.qrc b/tests/resources.qrc index 34df0af51..906905702 100644 --- a/tests/resources.qrc +++ b/tests/resources.qrc @@ -36,6 +36,9 @@ assets/images/chevron_red.svg assets/images/chevron_white.svg assets/images/collapse.svg + assets/images/contact_card_photo_hovered.svg + assets/images/contact_card_photo_normal.svg + assets/images/contact_card_photo_pressed.svg assets/images/contact_edit_hovered.svg assets/images/contact_edit_normal.svg assets/images/contact_edit_pressed.svg diff --git a/tests/src/app/Database.cpp b/tests/src/app/Database.cpp index cf1620738..b8cddd0c8 100644 --- a/tests/src/app/Database.cpp +++ b/tests/src/app/Database.cpp @@ -14,8 +14,9 @@ QStandardPaths::writableLocation(QStandardPaths::HomeLocation) #endif -#define DATABASE_PATH_FRIENDS_LIST ".linphone-friends.db" +#define DATABASE_PATH_AVATARS ".linphone/avatars/" #define DATABASE_PATH_CALL_HISTORY_LIST ".linphone-call-history.db" +#define DATABASE_PATH_FRIENDS_LIST ".linphone-friends.db" #define DATABASE_PATH_MESSAGE_HISTORY_LIST ".linphone-history.db" using namespace std; @@ -33,6 +34,16 @@ inline bool ensureDatabaseFilePathExists (const QString &path) { return file.exists() || file.open(QIODevice::ReadWrite); } +string Database::getAvatarsPath () { + QString path(DATABASES_PATH + "/" DATABASE_PATH_AVATARS); + QDir dir(path); + + if (!dir.exists() && !dir.mkpath(path)) + return ""; + + return Utils::qStringToLinphoneString(QDir::toNativeSeparators(path)); +} + inline string getDatabaseFilePath (const QString &filename) { QString path(DATABASES_PATH + "/"); path += filename; @@ -41,14 +52,14 @@ inline string getDatabaseFilePath (const QString &filename) { : ""; } -string Database::getFriendsListPath () { - return getDatabaseFilePath(DATABASE_PATH_FRIENDS_LIST); -} - string Database::getCallHistoryPath () { return getDatabaseFilePath(DATABASE_PATH_CALL_HISTORY_LIST); } +string Database::getFriendsListPath () { + return getDatabaseFilePath(DATABASE_PATH_FRIENDS_LIST); +} + string Database::getMessageHistoryPath () { return getDatabaseFilePath(DATABASE_PATH_MESSAGE_HISTORY_LIST); } diff --git a/tests/src/app/Database.hpp b/tests/src/app/Database.hpp index d1346b787..9369f5365 100644 --- a/tests/src/app/Database.hpp +++ b/tests/src/app/Database.hpp @@ -9,8 +9,10 @@ namespace Database { // Returns the databases paths. // If files cannot be created or are unavailable, a empty string is returned. // Use the directories separator of used OS. - std::string getFriendsListPath (); + std::string getAvatarsPath (); + std::string getCallHistoryPath (); + std::string getFriendsListPath (); std::string getMessageHistoryPath (); }; diff --git a/tests/src/components/contacts/ContactModel.cpp b/tests/src/components/contacts/ContactModel.cpp index 44a5409b0..e821caf4e 100644 --- a/tests/src/components/contacts/ContactModel.cpp +++ b/tests/src/components/contacts/ContactModel.cpp @@ -1,9 +1,28 @@ +#include +#include +#include +#include + +#include + +#include "../../app/Database.hpp" #include "../../utils.hpp" #include "ContactModel.hpp" +using namespace std; + // =================================================================== +inline shared_ptr getBelCard ( + const shared_ptr &linphone_friend +) { + shared_ptr vcard = linphone_friend->getVcard(); + return *reinterpret_cast *>(vcard.get()); +} + +// ------------------------------------------------------------------- + Presence::PresenceStatus ContactModel::getPresenceStatus () const { return m_presence_status; } @@ -18,6 +37,43 @@ QString ContactModel::getUsername () const { ); } +bool ContactModel::setAvatar (const QString &path) { + // Try to copy photo in avatars folder. + QFile file(path); + + if (!file.exists() || QImageReader::imageFormat(path).size() == 0) + return false; + + QFileInfo info(file); + QString file_id = QUuid::createUuid().toString() + "." + info.suffix(); + QString dest = Utils::linphoneStringToQString(Database::getAvatarsPath()) + + file_id; + + if (!file.copy(dest)) + return false; + + qInfo() << QStringLiteral("Update avatar of `%1`. (path=%2)") + .arg(getUsername()).arg(dest); + + // Remove oldest photos. + shared_ptr belCard = getBelCard(m_linphone_friend); + + for (const auto &photo : belCard->getPhotos()) { + qDebug() << Utils::linphoneStringToQString(photo->getValue()); + belCard->removePhoto(photo); + } + + // Update. + shared_ptr photo = + belcard::BelCardGeneric::create(); + photo->setValue(Utils::qStringToLinphoneString(file_id)); + belCard->addPhoto(photo); + + + emit contactUpdated(); + return true; +} + QString ContactModel::getSipAddress () const { return Utils::linphoneStringToQString( m_linphone_friend->getAddress()->asString() diff --git a/tests/src/components/contacts/ContactModel.hpp b/tests/src/components/contacts/ContactModel.hpp index 21eb561bb..588ac2823 100644 --- a/tests/src/components/contacts/ContactModel.hpp +++ b/tests/src/components/contacts/ContactModel.hpp @@ -23,19 +23,20 @@ class ContactModel : public QObject { Q_PROPERTY( QString avatar READ getAvatar + WRITE setAvatar NOTIFY contactUpdated ); Q_PROPERTY( Presence::PresenceStatus presenceStatus READ getPresenceStatus - CONSTANT + NOTIFY contactUpdated ); Q_PROPERTY( Presence::PresenceLevel presenceLevel READ getPresenceLevel - CONSTANT + NOTIFY contactUpdated ); Q_PROPERTY( @@ -60,6 +61,8 @@ private: return ""; } + bool setAvatar (const QString &path); + Presence::PresenceStatus getPresenceStatus () const; Presence::PresenceLevel getPresenceLevel () const; diff --git a/tests/ui/modules/Common/Styles/ForceScrollBarStyle.qml b/tests/ui/modules/Common/Styles/ForceScrollBarStyle.qml index f9d3d32d1..e765eefa2 100644 --- a/tests/ui/modules/Common/Styles/ForceScrollBarStyle.qml +++ b/tests/ui/modules/Common/Styles/ForceScrollBarStyle.qml @@ -9,7 +9,7 @@ QtObject { property color backgroundColor: Colors.g20 property QtObject contentItem: QtObject { - property int implicitHeight: 100 + property int implicitHeight: 8 property int implicitWidth: 8 property int radius: 10 } diff --git a/tests/ui/modules/Linphone/Contact/Avatar.qml b/tests/ui/modules/Linphone/Contact/Avatar.qml index edc42701c..3b286af1c 100644 --- a/tests/ui/modules/Linphone/Contact/Avatar.qml +++ b/tests/ui/modules/Linphone/Contact/Avatar.qml @@ -20,6 +20,10 @@ Item { // ----------------------------------------------------------------- + function isLoaded () { + return roundedImage.status === Image.Ready + } + function _computeInitials () { var result = username.match(_initialsRegex) diff --git a/tests/ui/scripts/Utils/utils.js b/tests/ui/scripts/Utils/utils.js index ae311bad1..b8e35f26a 100644 --- a/tests/ui/scripts/Utils/utils.js +++ b/tests/ui/scripts/Utils/utils.js @@ -388,6 +388,12 @@ function isInteger (integer) { // ------------------------------------------------------------------- +function isObject (object) { + return object !== null && typeof object === 'object' +} + +// ------------------------------------------------------------------- + function isString (string) { return typeof string === 'string' || string instanceof String } diff --git a/tests/ui/views/App/MainWindow/ContactEdit.qml b/tests/ui/views/App/MainWindow/ContactEdit.qml index c512a1614..3ebdb9d2b 100644 --- a/tests/ui/views/App/MainWindow/ContactEdit.qml +++ b/tests/ui/views/App/MainWindow/ContactEdit.qml @@ -1,5 +1,6 @@ import QtQuick 2.7 import QtQuick.Controls 2.0 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.3 import Common 1.0 @@ -20,10 +21,12 @@ ColumnLayout { sipAddress ) || sipAddress + property var _info: {} + // ----------------------------------------------------------------- function _removeContact () { - Utils.openConfirmDialog(this, { + Utils.openConfirmDialog(window, { descriptionText: qsTr('removeContactDescription'), exitHandler: function (status) { if (status) { @@ -35,10 +38,31 @@ ColumnLayout { }) } + function _setAvatar (path) { + if (!path) { + return + } + + if (Utils.isObject(_contact)) { + _contact.avatar = path.match(/^(?:file:\/\/)?(.*)$/)[1] + } + + // TODO: Not registered contact. + } + // ----------------------------------------------------------------- spacing: 0 + FileDialog { + id: avatarChooser + + folder: shortcuts.home + title: qsTr('avatarChooserTitle') + + onAccepted: _setAvatar(fileUrls[0]) + } + // ----------------------------------------------------------------- // Info bar. // ----------------------------------------------------------------- @@ -64,6 +88,22 @@ ColumnLayout { width: ContactEditStyle.infoBar.avatarSize username: LinphoneUtils.getContactUsername(_contact) + visible: isLoaded() + + MouseArea { + anchors.fill: parent + onClicked: avatarChooser.open() + } + } + + ActionButton { + Layout.preferredHeight: ContactEditStyle.infoBar.avatarSize + Layout.preferredWidth: ContactEditStyle.infoBar.avatarSize + + icon: 'contact_card_photo' + visible: !avatar.isLoaded() + + onClicked: avatarChooser.open() } Text { @@ -83,6 +123,7 @@ ColumnLayout { Layout.alignment: Qt.AlignRight iconSize: ContactEditStyle.infoBar.buttons.size spacing: ContactEditStyle.infoBar.buttons.spacing + visible: Utils.isObject(_contact) ActionButton { icon: 'history' @@ -107,16 +148,17 @@ ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true ScrollBar.vertical: ForceScrollBar {} + boundsBehavior: Flickable.StopAtBounds clip: true - contentHeight: content.height + contentHeight: infoList.height flickableDirection: Flickable.VerticalFlick ColumnLayout { anchors.left: parent.left - anchors.margins: 20 + anchors.margins: 40 anchors.right: parent.right - id: content + id: infoList ListForm { title: qsTr('sipAccounts')