Feature : Avatar from default account.

- Update SDK for accountRemoved callback and fix on setting default account.
- Update AccountGui from list modification and default account selection.
- Add Avatar provider for Qml.
- Create avatar file and store it into avatars folder.
- Delete old avatar file if replaced.
This commit is contained in:
Julien Wadel 2023-11-29 14:47:04 +01:00
parent 9e1e797d8c
commit b316074feb
14 changed files with 120 additions and 29 deletions

View file

@ -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);

View file

@ -71,7 +71,8 @@ void AccountList::setSelf(QSharedPointer<AccountList> me) {
});
});
});
mModelConnection->makeConnect(CoreModel::getInstance().get(), &CoreModel::defaultAccountChanged,
[this]() { mModelConnection->invokeToCore([this]() { defaultAccountChanged(); }); });
lUpdate();
}

View file

@ -49,6 +49,7 @@ public:
signals:
void lUpdate();
void haveAccountChanged();
void defaultAccountChanged();
private:
bool mHaveAccount = false;

View file

@ -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<AccountList *>(sourceModel())->getDefaultAccount();
AccountGui *AccountProxy::getDefaultAccount() {
if (!mDefaultAccount) mDefaultAccount = dynamic_cast<AccountList *>(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<AccountList *>(sourceModel())->getHaveAccount();
}

View file

@ -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<AccountList> mList;
};

View file

@ -20,10 +20,12 @@
#include "AccountModel.hpp"
#include <QDebug>
#include "core/path/Paths.hpp"
#include "model/core/CoreModel.hpp"
#include "tool/Utils.hpp"
#include "tool/providers/AvatarProvider.hpp"
#include <QDebug>
#include <QUrl>
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<linphone::Account>(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();
}

View file

@ -139,6 +139,14 @@ void CoreModel::setPathAfterStart() {
//---------------------------------------------------------------------------------------------------------------------------
void CoreModel::onAccountAdded(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) {
emit accountAdded(core, account);
}
void CoreModel::onAccountRemoved(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) {
emit accountRemoved(core, account);
}
void CoreModel::onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -195,6 +203,10 @@ void CoreModel::onConfiguringStatus(const std::shared_ptr<linphone::Core> &core,
const std::string &message) {
emit configuringStatus(core, status, message);
}
void CoreModel::onDefaultAccountChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) {
emit defaultAccountChanged(core, account);
}
void CoreModel::onDtmfReceived(const std::shared_ptr<linphone::Core> &lc,
const std::shared_ptr<linphone::Call> &call,
int dtmf) {
@ -234,10 +246,7 @@ void CoreModel::onMessagesReceived(const std::shared_ptr<linphone::Core> &core,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) {
emit messagesReceived(core, room, messages);
}
void CoreModel::onNewAccountAdded(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) {
emit accountAdded(core, account);
}
void CoreModel::onNewMessageReaction(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &chatRoom,
const std::shared_ptr<linphone::ChatMessage> &message,

View file

@ -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<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) override;
virtual void onAccountRemoved(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) override;
virtual void onAccountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -107,6 +110,8 @@ private:
virtual void onConfiguringStatus(const std::shared_ptr<linphone::Core> &core,
linphone::Config::ConfiguringState status,
const std::string &message) override;
virtual void onDefaultAccountChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) override;
virtual void onDtmfReceived(const std::shared_ptr<linphone::Core> &lc,
const std::shared_ptr<linphone::Call> &call,
int dtmf) override;
@ -130,8 +135,6 @@ private:
virtual void onMessagesReceived(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &room,
const std::list<std::shared_ptr<linphone::ChatMessage>> &messages) override;
virtual void onNewAccountAdded(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account) override;
virtual void onNewMessageReaction(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::ChatRoom> &chatRoom,
@ -159,7 +162,7 @@ private:
signals:
void accountAdded(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account);
void accountRemoved();
void accountRemoved(const std::shared_ptr<linphone::Core> &core, const std::shared_ptr<linphone::Account> &account);
void accountRegistrationStateChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account,
linphone::RegistrationState state,
@ -189,6 +192,8 @@ signals:
void configuringStatus(const std::shared_ptr<linphone::Core> &core,
linphone::Config::ConfiguringState status,
const std::string &message);
void defaultAccountChanged(const std::shared_ptr<linphone::Core> &core,
const std::shared_ptr<linphone::Account> &account);
void dtmfReceived(const std::shared_ptr<linphone::Core> &lc, const std::shared_ptr<linphone::Call> &call, int dtmf);
void
ecCalibrationResult(const std::shared_ptr<linphone::Core> &core, linphone::EcCalibratorStatus status, int delayMs);

View file

@ -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
)

View file

@ -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 <QImageReader>
// =============================================================================
@ -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;
}

View file

@ -53,6 +53,7 @@ public:
const QString &prepareTransfertAddress = "",
const QHash<QString, QString> &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);

View file

@ -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 &) {

View file

@ -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

@ -1 +1 @@
Subproject commit 477d4b7d56e256ce6581e6c31d12d3c7f49f9f9a
Subproject commit bf9106ee57b8a32aea2693f0fc69c2397a66d570