diff --git a/Linphone/core/App.cpp b/Linphone/core/App.cpp index a6571294..6bf24fbc 100644 --- a/Linphone/core/App.cpp +++ b/Linphone/core/App.cpp @@ -46,9 +46,9 @@ #include "model/object/VariantObject.hpp" #include "tool/Constants.hpp" #include "tool/Utils.hpp" -#include "tool/thread/Thread.hpp" - +#include "tool/providers/AvatarProvider.hpp" #include "tool/providers/ImageProvider.hpp" +#include "tool/thread/Thread.hpp" App::App(int &argc, char *argv[]) : SingleApplication(argc, argv, true, Mode::User | Mode::ExcludeAppPath | Mode::ExcludeAppVersion) { @@ -102,6 +102,7 @@ void App::init() { mEngine->rootContext()->setContextProperty("applicationDirPath", QGuiApplication::applicationDirPath()); initCppInterfaces(); mEngine->addImageProvider(ImageProvider::ProviderId, new ImageProvider()); + mEngine->addImageProvider(AvatarProvider::ProviderId, new AvatarProvider()); // Enable notifications. mNotifier = new Notifier(mEngine); diff --git a/Linphone/core/account/AccountList.cpp b/Linphone/core/account/AccountList.cpp index 516227ae..5cc089a9 100644 --- a/Linphone/core/account/AccountList.cpp +++ b/Linphone/core/account/AccountList.cpp @@ -71,7 +71,8 @@ void AccountList::setSelf(QSharedPointer me) { }); }); }); - + mModelConnection->makeConnect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged, + [this]() { mModelConnection->invokeToCore([this]() { defaultAccountChanged(); }); }); lUpdate(); } diff --git a/Linphone/core/account/AccountList.hpp b/Linphone/core/account/AccountList.hpp index 780e8ea7..f99fb1ef 100644 --- a/Linphone/core/account/AccountList.hpp +++ b/Linphone/core/account/AccountList.hpp @@ -49,6 +49,7 @@ public: signals: void lUpdate(); void haveAccountChanged(); + void defaultAccountChanged(); private: bool mHaveAccount = false; diff --git a/Linphone/core/account/AccountProxy.cpp b/Linphone/core/account/AccountProxy.cpp index 348b8292..256dfb27 100644 --- a/Linphone/core/account/AccountProxy.cpp +++ b/Linphone/core/account/AccountProxy.cpp @@ -25,7 +25,8 @@ AccountProxy::AccountProxy(QObject *parent) : SortFilterProxy(parent) { qDebug() << "[AccountProxy] new" << this; mList = AccountList::create(); - connect(mList.get(), &AccountList::countChanged, this, &AccountProxy::defaultAccountChanged); + connect(mList.get(), &AccountList::countChanged, this, &AccountProxy::resetDefaultAccount); + connect(mList.get(), &AccountList::defaultAccountChanged, this, &AccountProxy::resetDefaultAccount); connect(mList.get(), &AccountList::haveAccountChanged, this, &AccountProxy::haveAccountChanged); setSourceModel(mList.get()); sort(0); @@ -47,13 +48,20 @@ void AccountProxy::setFilterText(const QString &filter) { } } -AccountGui *AccountProxy::getDefaultAccount() const { - return dynamic_cast(sourceModel())->getDefaultAccount(); +AccountGui *AccountProxy::getDefaultAccount() { + if (!mDefaultAccount) mDefaultAccount = dynamic_cast(sourceModel())->getDefaultAccount(); + return mDefaultAccount; } void AccountProxy::setDefaultAccount(AccountGui *account) { } +// Reset the default account to let UI build its new object if needed. +void AccountProxy::resetDefaultAccount() { + mDefaultAccount = nullptr; + this->defaultAccountChanged(); // Warn the UI +} + bool AccountProxy::getHaveAccount() const { return dynamic_cast(sourceModel())->getHaveAccount(); } diff --git a/Linphone/core/account/AccountProxy.hpp b/Linphone/core/account/AccountProxy.hpp index 0b3c0378..5b8fc2dc 100644 --- a/Linphone/core/account/AccountProxy.hpp +++ b/Linphone/core/account/AccountProxy.hpp @@ -41,8 +41,9 @@ public: QString getFilterText() const; void setFilterText(const QString &filter); - AccountGui *getDefaultAccount() const; - void setDefaultAccount(AccountGui *account); + AccountGui *getDefaultAccount(); // Get a new object from List or give the stored one. + void setDefaultAccount(AccountGui *account); // TODO + void resetDefaultAccount(); // Reset the default account to let UI build its new object if needed. bool getHaveAccount() const; @@ -56,6 +57,7 @@ protected: virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; QString mFilterText; + AccountGui *mDefaultAccount = nullptr; // When null, a new UI object is build from List QSharedPointer mList; }; diff --git a/Linphone/model/account/AccountModel.cpp b/Linphone/model/account/AccountModel.cpp index 18ba8e88..ee7d94c0 100644 --- a/Linphone/model/account/AccountModel.cpp +++ b/Linphone/model/account/AccountModel.cpp @@ -20,10 +20,12 @@ #include "AccountModel.hpp" -#include - +#include "core/path/Paths.hpp" #include "model/core/CoreModel.hpp" #include "tool/Utils.hpp" +#include "tool/providers/AvatarProvider.hpp" +#include +#include DEFINE_ABSTRACT_OBJECT(AccountModel) @@ -48,6 +50,15 @@ void AccountModel::setPictureUri(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()); + if (!oldPictureUri.isEmpty()) { + QString appPrefix = QStringLiteral("image://%1/").arg(AvatarProvider::ProviderId); + if (oldPictureUri.startsWith(appPrefix)) { + oldPictureUri = Paths::getAvatarsDirPath() + oldPictureUri.mid(appPrefix.length()); + } + QFile oldPicture(oldPictureUri); + if (!oldPicture.remove()) qWarning() << log().arg("Cannot delete old avatar file at " + oldPictureUri); + } params->setPictureUri(Utils::appStringToCoreString(uri)); account->setParams(params); emit pictureUriChanged(uri); @@ -61,5 +72,4 @@ void AccountModel::setDefault() { mustBeInLinphoneThread(log().arg(Q_FUNC_INFO)); auto core = CoreModel::getInstance()->getCore(); core->setDefaultAccount(mMonitor); - emit CoreModel::getInstance()->defaultAccountChanged(); } diff --git a/Linphone/model/core/CoreModel.cpp b/Linphone/model/core/CoreModel.cpp index 68bd0fe6..d6221e9c 100644 --- a/Linphone/model/core/CoreModel.cpp +++ b/Linphone/model/core/CoreModel.cpp @@ -139,6 +139,14 @@ void CoreModel::setPathAfterStart() { //--------------------------------------------------------------------------------------------------------------------------- +void CoreModel::onAccountAdded(const std::shared_ptr &core, + const std::shared_ptr &account) { + emit accountAdded(core, account); +} +void CoreModel::onAccountRemoved(const std::shared_ptr &core, + const std::shared_ptr &account) { + emit accountRemoved(core, account); +} void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -195,6 +203,10 @@ void CoreModel::onConfiguringStatus(const std::shared_ptr &core, const std::string &message) { emit configuringStatus(core, status, message); } +void CoreModel::onDefaultAccountChanged(const std::shared_ptr &core, + const std::shared_ptr &account) { + emit defaultAccountChanged(core, account); +} void CoreModel::onDtmfReceived(const std::shared_ptr &lc, const std::shared_ptr &call, int dtmf) { @@ -234,10 +246,7 @@ void CoreModel::onMessagesReceived(const std::shared_ptr &core, const std::list> &messages) { emit messagesReceived(core, room, messages); } -void CoreModel::onNewAccountAdded(const std::shared_ptr &core, - const std::shared_ptr &account) { - emit accountAdded(core, account); -} + void CoreModel::onNewMessageReaction(const std::shared_ptr &core, const std::shared_ptr &chatRoom, const std::shared_ptr &message, diff --git a/Linphone/model/core/CoreModel.hpp b/Linphone/model/core/CoreModel.hpp index acb2b31e..721df83b 100644 --- a/Linphone/model/core/CoreModel.hpp +++ b/Linphone/model/core/CoreModel.hpp @@ -56,7 +56,6 @@ public: signals: void loggerInitialized(); - void defaultAccountChanged(); void friendAdded(); void friendRemoved(); @@ -74,6 +73,10 @@ private: //-------------------------------------------------------------------------------- // LINPHONE //-------------------------------------------------------------------------------- + virtual void onAccountAdded(const std::shared_ptr &core, + const std::shared_ptr &account) override; + virtual void onAccountRemoved(const std::shared_ptr &core, + const std::shared_ptr &account) override; virtual void onAccountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -107,6 +110,8 @@ private: virtual void onConfiguringStatus(const std::shared_ptr &core, linphone::Config::ConfiguringState status, const std::string &message) override; + virtual void onDefaultAccountChanged(const std::shared_ptr &core, + const std::shared_ptr &account) override; virtual void onDtmfReceived(const std::shared_ptr &lc, const std::shared_ptr &call, int dtmf) override; @@ -130,8 +135,6 @@ private: virtual void onMessagesReceived(const std::shared_ptr &core, const std::shared_ptr &room, const std::list> &messages) override; - virtual void onNewAccountAdded(const std::shared_ptr &core, - const std::shared_ptr &account) override; virtual void onNewMessageReaction(const std::shared_ptr &core, const std::shared_ptr &chatRoom, @@ -159,7 +162,7 @@ private: signals: void accountAdded(const std::shared_ptr &core, const std::shared_ptr &account); - void accountRemoved(); + void accountRemoved(const std::shared_ptr &core, const std::shared_ptr &account); void accountRegistrationStateChanged(const std::shared_ptr &core, const std::shared_ptr &account, linphone::RegistrationState state, @@ -189,6 +192,8 @@ signals: void configuringStatus(const std::shared_ptr &core, linphone::Config::ConfiguringState status, const std::string &message); + void defaultAccountChanged(const std::shared_ptr &core, + const std::shared_ptr &account); void dtmfReceived(const std::shared_ptr &lc, const std::shared_ptr &call, int dtmf); void ecCalibrationResult(const std::shared_ptr &core, linphone::EcCalibratorStatus status, int delayMs); diff --git a/Linphone/tool/CMakeLists.txt b/Linphone/tool/CMakeLists.txt index 6963882b..49ecefbb 100644 --- a/Linphone/tool/CMakeLists.txt +++ b/Linphone/tool/CMakeLists.txt @@ -6,6 +6,7 @@ list(APPEND _LINPHONEAPP_SOURCES tool/thread/SafeSharedPointer.hpp tool/thread/SafeConnection.cpp tool/thread/Thread.cpp + tool/providers/AvatarProvider.cpp tool/providers/ImageProvider.cpp ) diff --git a/Linphone/tool/Utils.cpp b/Linphone/tool/Utils.cpp index d4fc3831..093cf4c4 100644 --- a/Linphone/tool/Utils.cpp +++ b/Linphone/tool/Utils.cpp @@ -22,8 +22,11 @@ #include "core/App.hpp" #include "core/call/CallGui.hpp" +#include "core/path/Paths.hpp" #include "model/object/VariantObject.hpp" #include "model/tool/ToolModel.hpp" +#include "tool/providers/AvatarProvider.hpp" +#include // ============================================================================= @@ -78,3 +81,36 @@ VariantObject *Utils::haveAccount() { result->requestValue(); return result; } +QString Utils::createAvatar(const QUrl &fileUrl) { + QString filePath = fileUrl.toLocalFile(); + QString fileId; // uuid.ext + QString fileUri; // image://avatar/filename.ext + QFile file; + if (!filePath.isEmpty()) { + if (filePath.startsWith("image:")) { // No need to copy + fileUri = filePath; + } else { + file.setFileName(filePath); + if (!file.exists()) { + qWarning() << "[Utils] Avatar not found at " << filePath; + return ""; + } + if (QImageReader::imageFormat(filePath).size() == 0) { + qWarning() << "[Utils] Avatar extension not supported by QImageReader for " << filePath; + return ""; + } + QFileInfo info(file); + QString uuid = QUuid::createUuid().toString(); + fileId = QStringLiteral("%1.%2") + .arg(uuid.mid(1, uuid.length() - 2)) // Remove `{}`. + .arg(info.suffix()); + fileUri = QStringLiteral("image://%1/%2").arg(AvatarProvider::ProviderId).arg(fileId); + QString dest = Paths::getAvatarsDirPath() + fileId; + if (!file.copy(dest)) { + qWarning() << "[Utils] Avatar couldn't be created to " << dest; + return ""; + } + } + } + return fileUri; +} diff --git a/Linphone/tool/Utils.hpp b/Linphone/tool/Utils.hpp index 6a8dfa8c..81197270 100644 --- a/Linphone/tool/Utils.hpp +++ b/Linphone/tool/Utils.hpp @@ -53,6 +53,7 @@ public: const QString &prepareTransfertAddress = "", const QHash &headers = {}); Q_INVOKABLE static VariantObject *haveAccount(); + Q_INVOKABLE static QString createAvatar(const QUrl &fileUrl); // Return the avatar path static inline QString coreStringToAppString(const std::string &str) { if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) return QString::fromStdString(str); diff --git a/Linphone/tool/providers/AvatarProvider.cpp b/Linphone/tool/providers/AvatarProvider.cpp index c63a731d..a7acdd56 100644 --- a/Linphone/tool/providers/AvatarProvider.cpp +++ b/Linphone/tool/providers/AvatarProvider.cpp @@ -19,8 +19,6 @@ */ #include "core/path/Paths.hpp" -#include "core/utils/Constants.hpp" -// #include "tool/Utils.hpp" #include "AvatarProvider.hpp" @@ -30,9 +28,7 @@ const QString AvatarProvider::ProviderId = "avatar"; AvatarProvider::AvatarProvider() : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) { - const auto &str = Paths::getAvatarsDirPath().toStdString(); - if (Constants::LinphoneLocaleEncoding == QString("UTF-8")) mAvatarsPath = QString::fromStdString(str); - else mAvatarsPath = QString::fromLocal8Bit(str.c_str(), int(str.size())); + mAvatarsPath = Paths::getAvatarsDirPath(); } QImage AvatarProvider::requestImage(const QString &id, QSize *size, const QSize &) { diff --git a/Linphone/view/App/Layout/MainLayout.qml b/Linphone/view/App/Layout/MainLayout.qml index 3757ebc9..fbb394f5 100644 --- a/Linphone/view/App/Layout/MainLayout.qml +++ b/Linphone/view/App/Layout/MainLayout.qml @@ -2,11 +2,14 @@ * Qml template used for welcome and login/register pages **/ +import QtCore import QtQuick 2.15 import QtQuick.Layouts 1.3 import QtQuick.Controls 2.2 as Control +import QtQuick.Dialogs import Linphone +import UtilsCpp Item { id: mainItem @@ -36,18 +39,35 @@ Item { placeholderText: qsTr("Rechercher un contact, appeler ou envoyer un message...") } Control.Button { + id: avatarButton + AccountProxy{ + id: accountProxy + property bool haveAvatar: defaultAccount && defaultAccount.core.pictureUri || false + } + Layout.preferredWidth: 30 Layout.preferredHeight: 30 background: Item { visible: false } contentItem: Image { - //avatar - source: AppIcons.welcomeLinphoneLogo - // width: 30 - // height: 30 + id: avatar + source: accountProxy.haveAvatar ? accountProxy.defaultAccount.core.pictureUri : AppIcons.welcomeLinphoneLogo fillMode: Image.PreserveAspectFit } + onClicked: { + fileDialog.open() + } + FileDialog { + id: fileDialog + currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + var avatarPath = UtilsCpp.createAvatar( selectedFile ) + if(avatarPath){ + accountProxy.defaultAccount.core.pictureUri = avatarPath + } + } + } } Control.Button { enabled: false diff --git a/external/linphone-sdk b/external/linphone-sdk index 477d4b7d..bf9106ee 160000 --- a/external/linphone-sdk +++ b/external/linphone-sdk @@ -1 +1 @@ -Subproject commit 477d4b7d56e256ce6581e6c31d12d3c7f49f9f9a +Subproject commit bf9106ee57b8a32aea2693f0fc69c2397a66d570