From 173cf2be50b794e41ead9aa5bf49fde00c755dec Mon Sep 17 00:00:00 2001 From: Ming Rui Zhang <mingrui.zhang@savoirfairelinux.com> Date: Mon, 19 Oct 2020 14:51:31 -0400 Subject: [PATCH] migration: use image provider to show avatar image 1. Use avatarimageprovider 2. Remove redundant base64 code Change-Id: I2a2517890e95b4a9f9a363fbea2251d6d5dd1c8f --- CMakeLists.txt | 2 - jami-qt.pro | 3 +- qml.qrc | 2 +- src/accountadapter.cpp | 6 +- src/accountadapter.h | 2 +- src/accountlistmodel.cpp | 35 +++- src/accountlistmodel.h | 15 +- src/accountstomigratelistmodel.cpp | 4 - src/accountstomigratelistmodel.h | 9 +- src/avatarimageprovider.h | 71 +++++++ src/bannedlistmodel.cpp | 7 - src/bannedlistmodel.h | 2 +- .../AccountMigrationDialog.qml | 16 +- src/commoncomponents/AvatarImage.qml | 183 ++++++++++++++++++ src/commoncomponents/PhotoboothView.qml | 89 +++++---- src/conversationsadapter.cpp | 11 +- src/conversationsadapter.h | 1 + src/lrcinstance.h | 9 - src/mainapplication.cpp | 6 +- src/mainview/MainView.qml | 4 +- src/mainview/components/AccountComboBox.qml | 39 ++-- .../components/AccountComboBoxPopup.qml | 33 +--- src/mainview/components/AudioCallPage.qml | 11 +- src/mainview/components/ContactPicker.qml | 2 - .../components/ContactPickerItemDelegate.qml | 7 +- .../ConversationSmartListUserImage.qml | 68 ------- .../components/ConversationSmartListView.qml | 2 + .../ConversationSmartListViewItemDelegate.qml | 23 ++- .../components/ParticipantOverlay.qml | 1 + src/mainview/components/UserInfoCallPage.qml | 11 +- src/mainview/components/UserProfile.qml | 15 +- src/messagesadapter.cpp | 4 +- src/pixbufmanipulator.cpp | 127 ------------ src/pixbufmanipulator.h | 49 ----- src/previewrenderer.cpp | 18 -- src/previewrenderer.h | 3 - src/settingsadapter.cpp | 25 +-- src/settingsadapter.h | 3 +- .../components/AccountProfile.qml | 10 +- .../components/BannedContacts.qml | 1 - .../components/BannedItemDelegate.qml | 10 +- src/smartlistmodel.cpp | 72 +++++-- src/smartlistmodel.h | 16 +- src/utils.cpp | 84 +++++--- src/utils.h | 7 +- src/utilsadapter.cpp | 17 -- src/utilsadapter.h | 2 - src/wizardview/WizardView.qml | 7 +- .../components/CreateSIPAccountPage.qml | 2 - src/wizardview/components/ProfilePage.qml | 5 +- 50 files changed, 588 insertions(+), 563 deletions(-) create mode 100644 src/avatarimageprovider.h create mode 100644 src/commoncomponents/AvatarImage.qml delete mode 100644 src/mainview/components/ConversationSmartListUserImage.qml delete mode 100644 src/pixbufmanipulator.cpp delete mode 100644 src/pixbufmanipulator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f9287b6c..ca7666e19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,6 @@ set(COMMON_SOURCES src/main.cpp src/smartlistmodel.cpp src/utils.cpp - src/pixbufmanipulator.cpp src/rendermanager.cpp src/connectivitymonitor.cpp src/mainapplication.cpp @@ -85,7 +84,6 @@ set(COMMON_HEADERS src/globalsystemtray.h src/appsettingsmanager.h src/webchathelpers.h - src/pixbufmanipulator.h src/rendermanager.h src/connectivitymonitor.h src/jamiavatartheme.h diff --git a/jami-qt.pro b/jami-qt.pro index 20dfb4945..473442d67 100644 --- a/jami-qt.pro +++ b/jami-qt.pro @@ -111,6 +111,7 @@ unix { # Input HEADERS += \ + src/avatarimageprovider.h \ src/networkmanager.h \ src/smartlistmodel.h \ src/updatemanager.h \ @@ -123,7 +124,6 @@ HEADERS += \ src/globalsystemtray.h \ src/appsettingsmanager.h \ src/webchathelpers.h \ - src/pixbufmanipulator.h \ src/rendermanager.h \ src/connectivitymonitor.h \ src/jamiavatartheme.h \ @@ -168,7 +168,6 @@ SOURCES += \ src/main.cpp \ src/smartlistmodel.cpp \ src/utils.cpp \ - src/pixbufmanipulator.cpp \ src/rendermanager.cpp \ src/connectivitymonitor.cpp \ src/mainapplication.cpp \ diff --git a/qml.qrc b/qml.qrc index e9cc0f527..1c03e350e 100644 --- a/qml.qrc +++ b/qml.qrc @@ -97,7 +97,6 @@ <file>src/mainview/components/ProjectCreditsScrollView.qml</file> <file>src/mainview/components/AccountComboBoxPopup.qml</file> <file>src/mainview/components/ConversationSmartListViewItemDelegate.qml</file> - <file>src/mainview/components/ConversationSmartListUserImage.qml</file> <file>src/mainview/components/SidePanelTabBar.qml</file> <file>src/mainview/components/WelcomePageQrDialog.qml</file> <file>src/commoncomponents/GeneralMenuItem.qml</file> @@ -137,5 +136,6 @@ <file>src/commoncomponents/SimpleMessageDialog.qml</file> <file>src/commoncomponents/ResponsiveImage.qml</file> <file>src/commoncomponents/PresenceIndicator.qml</file> + <file>src/commoncomponents/AvatarImage.qml</file> </qresource> </RCC> diff --git a/src/accountadapter.cpp b/src/accountadapter.cpp index 269ee2ef3..f88a4ade0 100644 --- a/src/accountadapter.cpp +++ b/src/accountadapter.cpp @@ -373,13 +373,15 @@ AccountAdapter::connectAccount(const QString& accountId) &lrc::api::NewAccountModel::profileUpdated, [this](const QString& accountId) { if (LRCInstance::getCurrAccId() == accountId) - emit accountStatusChanged(); + emit accountStatusChanged(accountId); }); accountStatusChangedConnection_ = QObject::connect(accInfo.accountModel, &lrc::api::NewAccountModel::accountStatusChanged, - [this] { emit accountStatusChanged(); }); + [this](const QString& accountId) { + emit accountStatusChanged(accountId); + }); contactAddedConnection_ = QObject::connect(accInfo.contactModel.get(), diff --git a/src/accountadapter.h b/src/accountadapter.h index 291f067a4..3d45333b3 100644 --- a/src/accountadapter.h +++ b/src/accountadapter.h @@ -110,7 +110,7 @@ signals: /* * Trigger other components to reconnect account related signals. */ - void accountStatusChanged(); + void accountStatusChanged(QString accountId = {}); void updateConversationForAddedContact(); /* * send report failure to QML to make it show the right UI state . diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index 389037a66..c397b0f9e 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -21,10 +21,7 @@ #include <QDateTime> -#include "globalinstances.h" - #include "lrcinstance.h" -#include "pixbufmanipulator.h" #include "utils.h" AccountListModel::AccountListModel(QObject* parent) @@ -68,6 +65,8 @@ AccountListModel::data(const QModelIndex& index, int role) const auto& accountInfo = LRCInstance::accountModel().getAccountInfo(accountList.at(index.row())); + // Since we are using image provider right now, image url representation should be unique to + // be able to use the image cache, account avatar will only be updated once PictureUid changed switch (role) { case Role::Alias: return QVariant(Utils::bestNameForAccount(accountInfo)); @@ -77,11 +76,10 @@ AccountListModel::data(const QModelIndex& index, int role) const return QVariant(static_cast<int>(accountInfo.profileInfo.type)); case Role::Status: return QVariant(static_cast<int>(accountInfo.status)); - case Role::Picture: - return QString::fromLatin1( - Utils::QImageToByteArray(Utils::accountPhoto(accountInfo)).toBase64().data()); case Role::ID: return QVariant(accountInfo.id); + case Role::PictureUid: + return avatarUidMap_[accountInfo.id]; } return QVariant(); } @@ -92,10 +90,10 @@ AccountListModel::roleNames() const QHash<int, QByteArray> roles; roles[Alias] = "Alias"; roles[Username] = "Username"; - roles[Picture] = "Picture"; roles[Type] = "Type"; roles[Status] = "Status"; roles[ID] = "ID"; + roles[PictureUid] = "PictureUid"; return roles; } @@ -134,5 +132,28 @@ void AccountListModel::reset() { beginResetModel(); + fillAvatarUidMap(LRCInstance::accountModel().getAccountList()); endResetModel(); } + +void +AccountListModel::updateAvatarUid(const QString& accountId) +{ + avatarUidMap_[accountId] = Utils::generateUid(); +} + +void +AccountListModel::fillAvatarUidMap(const QStringList& accountList) +{ + if (accountList.size() == 0) { + avatarUidMap_.clear(); + return; + } + + if (avatarUidMap_.isEmpty() || accountList.size() != avatarUidMap_.size()) { + for (int i = 0; i < accountList.size(); ++i) { + if (!avatarUidMap_.contains(accountList.at(i))) + avatarUidMap_.insert(accountList.at(i), Utils::generateUid()); + } + } +} diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h index 6741871d1..b1d247892 100644 --- a/src/accountlistmodel.h +++ b/src/accountlistmodel.h @@ -30,7 +30,7 @@ class AccountListModel : public QAbstractListModel Q_OBJECT public: - enum Role { Alias = Qt::UserRole + 1, Username, Picture, Type, Status, ID }; + enum Role { Alias = Qt::UserRole + 1, Username, Type, Status, ID, PictureUid }; Q_ENUM(Role) explicit AccountListModel(QObject* parent = 0); @@ -55,4 +55,17 @@ public: * This function is to reset the model when there's new account added. */ Q_INVOKABLE void reset(); + + /* + * This function is to update avatar uuid when there's an avatar changed. + */ + Q_INVOKABLE void updateAvatarUid(const QString& accountId); + +private: + /* + * Give a uuid for each account avatar and it will serve PictureUid role + */ + void fillAvatarUidMap(const QStringList& accountList); + + QMap<QString, QString> avatarUidMap_; }; diff --git a/src/accountstomigratelistmodel.cpp b/src/accountstomigratelistmodel.cpp index 98fd1a99c..be9309fc2 100644 --- a/src/accountstomigratelistmodel.cpp +++ b/src/accountstomigratelistmodel.cpp @@ -92,9 +92,6 @@ AccountsToMigrateListModel::data(const QModelIndex& index, int role) const return QVariant(avatarInfo.confProperties.username); case Role::Alias: return QVariant(LRCInstance::accountModel().getAccountInfo(accountId).profileInfo.alias); - case Role::Picture: - return QString::fromLatin1( - Utils::QImageToByteArray(Utils::accountPhoto(avatarInfo)).toBase64().data()); } return QVariant(); } @@ -108,7 +105,6 @@ AccountsToMigrateListModel::roleNames() const roles[ManagerUri] = "ManagerUri"; roles[Username] = "Username"; roles[Alias] = "Alias"; - roles[Picture] = "Picture"; return roles; } diff --git a/src/accountstomigratelistmodel.h b/src/accountstomigratelistmodel.h index 71abed754..6a35f80d1 100644 --- a/src/accountstomigratelistmodel.h +++ b/src/accountstomigratelistmodel.h @@ -31,14 +31,7 @@ class AccountsToMigrateListModel : public QAbstractListModel { Q_OBJECT public: - enum Role { - Account_ID = Qt::UserRole + 1, - ManagerUsername, - ManagerUri, - Username, - Alias, - Picture - }; + enum Role { Account_ID = Qt::UserRole + 1, ManagerUsername, ManagerUri, Username, Alias }; Q_ENUM(Role) explicit AccountsToMigrateListModel(QObject* parent = 0); diff --git a/src/avatarimageprovider.h b/src/avatarimageprovider.h new file mode 100644 index 000000000..b0cafc1c2 --- /dev/null +++ b/src/avatarimageprovider.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "utils.h" + +#include <QImage> +#include <QQuickImageProvider> + +class AvatarImageProvider : public QObject, public QQuickImageProvider +{ +public: + AvatarImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image, + QQmlImageProviderBase::ForceAsynchronousImageLoading) + {} + + /* + * Request function + * id could be + * 1. account_ + account id + * 2. file_ + file path + * 3. contact_+ contact uri + * 4. conversation_+ conversation uid + */ + QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override + { + Q_UNUSED(size) + + auto idInfo = id.split("_"); + // Id type -> account_ + auto idType = idInfo[1]; + // Id content -> every after account_ + auto idContent = id.mid(id.indexOf(idType) + idType.length() + 1); + + if (idContent.isEmpty()) + return QImage(); + + if (idType == "account") { + return Utils::accountPhoto(LRCInstance::accountModel().getAccountInfo(idContent), + requestedSize); + } else if (idType == "conversation") { + auto* convModel = LRCInstance::getCurrentAccountInfo().conversationModel.get(); + const auto& conv = convModel->getConversationForUID(idContent); + return Utils::contactPhoto(conv.participants[0], requestedSize); + } else if (idType == "contact") { + return Utils::contactPhoto(idContent, requestedSize); + } else { + auto image = Utils::cropImage(QImage(idContent)); + return image.scaled(requestedSize, + Qt::KeepAspectRatioByExpanding, + Qt::SmoothTransformation); + } + } +}; diff --git a/src/bannedlistmodel.cpp b/src/bannedlistmodel.cpp index b07e6cb64..684f5b443 100644 --- a/src/bannedlistmodel.cpp +++ b/src/bannedlistmodel.cpp @@ -61,12 +61,6 @@ BannedListModel::data(const QModelIndex& index, int role) const return QVariant(contactInfo.registeredName); case Role::ContactID: return QVariant(contactInfo.profileInfo.uri); - case Role::ContactPicture: - QImage avatarImage = Utils::fallbackAvatar(contactInfo.profileInfo.uri, - contactInfo.registeredName, - QSize(48, 48)); - - return QString::fromLatin1(Utils::QImageToByteArray(avatarImage).toBase64().data()); } return QVariant(); } @@ -77,7 +71,6 @@ BannedListModel::roleNames() const QHash<int, QByteArray> roles; roles[ContactName] = "ContactName"; roles[ContactID] = "ContactID"; - roles[ContactPicture] = "ContactPicture"; return roles; } diff --git a/src/bannedlistmodel.h b/src/bannedlistmodel.h index f664b9b55..28a2951e4 100644 --- a/src/bannedlistmodel.h +++ b/src/bannedlistmodel.h @@ -27,7 +27,7 @@ class BannedListModel : public QAbstractListModel BannedListModel(const BannedListModel& cpy); public: - enum Role { ContactName = Qt::UserRole + 1, ContactID, ContactPicture }; + enum Role { ContactName = Qt::UserRole + 1, ContactID }; Q_ENUM(Role) explicit BannedListModel(QObject* parent = nullptr); diff --git a/src/commoncomponents/AccountMigrationDialog.qml b/src/commoncomponents/AccountMigrationDialog.qml index 838f11319..e9e8aae43 100644 --- a/src/commoncomponents/AccountMigrationDialog.qml +++ b/src/commoncomponents/AccountMigrationDialog.qml @@ -42,7 +42,6 @@ Window { property bool nonOperationClosing: true property bool successState : true - property string imgBase64: "" signal accountMigrationFinished @@ -88,8 +87,7 @@ Window { accountID = accountsToMigrateListModel.data(accountsToMigrateListModel.index( 0, 0), AccountsToMigrateListModel.Account_ID) - imgBase64 = accountsToMigrateListModel.data(accountsToMigrateListModel.index( - 0, 0), AccountsToMigrateListModel.Picture) + avatarImg.updateImage(accountID) connectionMigrationEnded.enabled = false migrationPushButton.enabled = false @@ -284,17 +282,13 @@ Window { anchors.fill: parent color: "transparent" - Image { + AvatarImage { id: avatarImg anchors.fill: parent - source: { - if (imgBase64.length === 0) { - return "" - } else { - return "data:image/png;base64," + imgBase64 - } - } + + showPresenceIndicator: false + fillMode: Image.PreserveAspectCrop layer.enabled: true layer.effect: OpacityMask { diff --git a/src/commoncomponents/AvatarImage.qml b/src/commoncomponents/AvatarImage.qml new file mode 100644 index 000000000..28497a438 --- /dev/null +++ b/src/commoncomponents/AvatarImage.qml @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2020 by Savoir-faire Linux + * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> + * + * 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 <https://www.gnu.org/licenses/>. + */ + +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Window 2.14 +import net.jami.Models 1.0 + +Item { + id: root + + // FromUrl here is for grabToImage image url + enum Mode { + FromAccount = 0, + FromFile, + FromContactUri, + FromConvUid, + FromUrl, + Default + } + + property alias fillMode: rootImage.fillMode + property alias sourceSize: rootImage.sourceSize + property int mode: AvatarImage.Mode.FromAccount + property string imageProviderIdPrefix: { + switch(mode) { + case AvatarImage.Mode.FromAccount: + return "account_" + case AvatarImage.Mode.FromFile: + return "file_" + case AvatarImage.Mode.FromContactUri: + return "contact_" + case AvatarImage.Mode.FromConvUid: + return "conversation_" + default: + return "" + } + } + + // Full request url example: forceUpdateUrl_xxxxxxx_account_xxxxxxxx + property string imageProviderUrl: "image://avatarImage/" + forceUpdateUrl + "_" + + imageProviderIdPrefix + property string imageId: "" + property string defaultImgUrl: "qrc:/images/default_avatar_overlay.svg" + property string forceUpdateUrl: Date.now() + property alias presenceStatus: presenceIndicator.status + property bool showPresenceIndicator: true + property int unreadMessagesCount: 0 + + signal imageIsReady + + function updateImage(updatedId, oneTimeForceUpdateUrl) { + imageId = updatedId + if (oneTimeForceUpdateUrl === undefined) + forceUpdateUrl = Date.now() + else + forceUpdateUrl = oneTimeForceUpdateUrl + + if (mode === AvatarImage.Mode.FromUrl) + rootImage.source = imageId + else if (imageId) + rootImage.source = imageProviderUrl + imageId + } + + onModeChanged: { + if (mode === AvatarImage.Mode.Default) + rootImage.source = defaultImgUrl + } + + Image { + id: rootImage + + anchors.fill: root + + smooth: false + antialiasing: true + + sourceSize.width: Math.max(24, width) + sourceSize.height: Math.max(24, height) + + fillMode: Image.PreserveAspectFit + + onStatusChanged: { + if (status === Image.Ready) { + rootImageOverlay.state = "" + rootImageOverlay.state = "rootImageLoading" + } + } + + Component.onCompleted: { + if (imageId) + return source = imageProviderUrl + imageId + return source = "" + } + + Image { + id: rootImageOverlay + + anchors.fill: rootImage + + smooth: false + antialiasing: true + + sourceSize.width: Math.max(24, width) + sourceSize.height: Math.max(24, height) + + fillMode: Image.PreserveAspectFit + + onOpacityChanged: { + if (opacity === 0) + source = rootImage.source + } + + onStatusChanged: { + if (status === Image.Ready && opacity === 0) { + opacity = 1 + root.imageIsReady() + } + } + + states: State { + name: "rootImageLoading" + PropertyChanges { target: rootImageOverlay; opacity: 0} + } + + transitions: Transition { + NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 400} + } + } + } + + PresenceIndicator { + id: presenceIndicator + + anchors.right: root.right + anchors.bottom: root.bottom + + size: root.width * 0.3 + + visible: showPresenceIndicator + } + + Rectangle { + id: unreadMessageCountRect + + anchors.right: root.right + anchors.top: root.top + + width: root.width * 0.3 + height: root.width * 0.3 + + visible: unreadMessagesCount > 0 + + Text { + id: unreadMessageCounttext + + anchors.centerIn: unreadMessageCountRect + + text: unreadMessagesCount > 9 ? "…" : unreadMessagesCount + color: "white" + font.pointSize: JamiTheme.textFontSize - 2 + } + + radius: 30 + color: JamiTheme.notificationRed + } + +} diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml index f28004938..1b7e2a0ef 100644 --- a/src/commoncomponents/PhotoboothView.qml +++ b/src/commoncomponents/PhotoboothView.qml @@ -10,9 +10,10 @@ import net.jami.Adapters 1.0 ColumnLayout { property bool takePhotoState: false property bool hasAvatar: false - property bool isDefaultIcon: false - property string imgBase64: "" + // saveToConfig is to specify whether the image should be saved to account config + property bool saveToConfig: false property string fileName: "" + property var boothImg: "" property int boothWidth: 224 @@ -20,9 +21,6 @@ ColumnLayout { buttonsRowLayout.height + JamiTheme.preferredMarginSize / 2 - signal imageAcquired - signal imageCleared - function startBooth(force = false){ hasAvatar = false AccountAdapter.startPreviewing(force) @@ -39,12 +37,15 @@ ColumnLayout { takePhotoState = false } - function setAvatarPixmap(avatarPixmapBase64, defaultValue = false){ - imgBase64 = avatarPixmapBase64 - stopBooth() - if(defaultValue){ - isDefaultIcon = defaultValue - } + function setAvatarImage(mode = AvatarImage.Mode.FromAccount, + imageId = AccountAdapter.currentAccountId){ + if (mode === AvatarImage.Mode.Default) + boothImg = "" + + avatarImg.mode = mode + + if (imageId) + avatarImg.updateImage(imageId) } onVisibleChanged: { @@ -68,14 +69,13 @@ ColumnLayout { onAccepted: { fileName = file if (fileName.length === 0) { - imageCleared() + SettingsAdapter.clearCurrentAvatar() + setAvatarImage() return } - imgBase64 = UtilsAdapter.getCroppedImageBase64FromFile( - UtilsAdapter.getAbsPath(fileName), - boothWidth) - imageAcquired() - stopBooth() + + setAvatarImage(AvatarImage.Mode.FromFile, + UtilsAdapter.getAbsPath(fileName)) } } @@ -96,29 +96,40 @@ ColumnLayout { color: "grey" radius: height / 2 - Image { + AvatarImage { id: avatarImg anchors.fill: parent - source: { - if(imgBase64.length === 0){ - return "qrc:/images/default_avatar_overlay.svg" - } else { - return "data:image/png;base64," + imgBase64 - } - } + + imageId: AccountAdapter.currentAccountId + + showPresenceIndicator: false + fillMode: Image.PreserveAspectCrop + layer.enabled: true layer.effect: OpacityMask { maskSource: Rectangle { width: avatarImg.width height: avatarImg.height radius: { - var size = ((avatarImg.width <= avatarImg.height)? avatarImg.width:avatarImg.height) - return size /2 + var size = ((avatarImg.width <= avatarImg.height) ? + avatarImg.width:avatarImg.height) + return size / 2 } } } + + onImageIsReady: { + // Once image is loaded (updated), save to boothImg + avatarImg.grabToImage(function(result) { + if (mode !== AvatarImage.Mode.Default) + boothImg = result.image + + if (saveToConfig) + SettingsAdapter.setCurrAccAvatar(result.image) + }) + } } } } @@ -126,9 +137,7 @@ ColumnLayout { PhotoboothPreviewRender { id:previewWidget - onHideBooth:{ - stopBooth() - } + onHideBooth: stopBooth() visible: takePhotoState focus: visible @@ -143,8 +152,9 @@ ColumnLayout { width: previewWidget.width height: previewWidget.height radius: { - var size = ((previewWidget.width <= previewWidget.height)? previewWidget.width:previewWidget.height) - return size /2 + var size = ((previewWidget.width <= previewWidget.height) ? + previewWidget.width:previewWidget.height) + return size / 2 } } } @@ -191,7 +201,6 @@ ColumnLayout { radius: height / 6 source: { - if(takePhotoState) { toolTipText = qsTr("Take photo") return cameraAltIconUrl @@ -205,9 +214,9 @@ ColumnLayout { return addPhotoIconUrl } } + onClicked: { if(!takePhotoState){ - imageCleared() startBooth() return } else { @@ -215,11 +224,13 @@ ColumnLayout { flashOverlay.visible = true flashAnimation.restart() - // run concurrent function call to take photo - imgBase64 = previewWidget.takeCroppedPhotoToBase64(boothWidth) - hasAvatar = true - imageAcquired() - stopBooth() + previewWidget.grabToImage(function(result) { + + setAvatarImage(AvatarImage.Mode.FromUrl, result.url) + + hasAvatar = true + stopBooth() + }) } } } diff --git a/src/conversationsadapter.cpp b/src/conversationsadapter.cpp index b9333b272..27d836400 100644 --- a/src/conversationsadapter.cpp +++ b/src/conversationsadapter.cpp @@ -209,6 +209,14 @@ ConversationsAdapter::connectConversationModel(bool updateFilter) emit modelSorted(QVariant::fromValue(conversation.uid)); }); + contactProfileUpdatedConnection_ + = QObject::connect(LRCInstance::getCurrentAccountInfo().contactModel.get(), + &lrc::api::ContactModel::profileUpdated, + [this](const QString& contactUri) { + conversationSmartListModel_->updateContactAvatarUid(contactUri); + emit updateListViewRequested(); + }); + modelUpdatedConnection_ = QObject::connect(currentConversationModel, &lrc::api::ConversationModel::conversationUpdated, [this](const QString& convUid) { @@ -262,7 +270,7 @@ ConversationsAdapter::connectConversationModel(bool updateFilter) &lrc::api::ConversationModel::searchStatusChanged, [this](const QString& status) { emit showSearchStatus(status); }); - // This connection is ideal when separated search results list. + // This connection is ideal when separated search results list. // This signal is guaranteed to fire just after filterChanged during a search if results are // changed, and once before filterChanged when calling setFilter. // NOTE: Currently, when searching, the entire conversation list will be copied 2-3 times each @@ -295,6 +303,7 @@ ConversationsAdapter::disconnectConversationModel() QObject::disconnect(interactionRemovedConnection_); QObject::disconnect(searchStatusChangedConnection_); QObject::disconnect(searchResultUpdatedConnection_); + QObject::disconnect(contactProfileUpdatedConnection_); } void diff --git a/src/conversationsadapter.h b/src/conversationsadapter.h index abd97368a..6fc23a0a0 100644 --- a/src/conversationsadapter.h +++ b/src/conversationsadapter.h @@ -82,6 +82,7 @@ private: QMetaObject::Connection newConversationConnection_; QMetaObject::Connection conversationRemovedConnection_; QMetaObject::Connection conversationClearedConnection; + QMetaObject::Connection contactProfileUpdatedConnection_; QMetaObject::Connection selectedCallChanged_; QMetaObject::Connection smartlistSelectionConnection_; QMetaObject::Connection interactionRemovedConnection_; diff --git a/src/lrcinstance.h b/src/lrcinstance.h index ebebd20da..909806e83 100644 --- a/src/lrcinstance.h +++ b/src/lrcinstance.h @@ -336,15 +336,6 @@ public: return -1; } - static const QPixmap getCurrAccPixmap() - { - return instance() - .accountListModel_ - .data(instance().accountListModel_.index(getCurrentAccountIndex()), - AccountListModel::Role::Picture) - .value<QPixmap>(); - } - static void setAvatarForAccount(const QPixmap& avatarPixmap, const QString& accountID) { QByteArray ba; diff --git a/src/mainapplication.cpp b/src/mainapplication.cpp index 654387d2d..33b3f2d4c 100644 --- a/src/mainapplication.cpp +++ b/src/mainapplication.cpp @@ -26,10 +26,8 @@ #include "globalsystemtray.h" #include "qmlregister.h" #include "qrimageprovider.h" -#include "pixbufmanipulator.h" #include "tintedbuttonimageprovider.h" - -#include "globalinstances.h" +#include "avatarimageprovider.h" #include <QAction> #include <QCommandLineParser> @@ -148,7 +146,6 @@ MainApplication::init() gnutls_global_init(); #endif - GlobalInstances::setPixmapManipulator(std::make_unique<PixbufManipulator>()); initLrc(results[opts::UPDATEURL].toString(), connectivityMonitor_); #ifdef Q_OS_WIN @@ -322,6 +319,7 @@ MainApplication::initQmlEngine() engine_->addImageProvider(QLatin1String("qrImage"), new QrImageProvider()); engine_->addImageProvider(QLatin1String("tintedPixmap"), new TintedButtonImageProvider()); + engine_->addImageProvider(QLatin1String("avatarImage"), new AvatarImageProvider()); engine_->load(QUrl(QStringLiteral("qrc:/src/MainApplicationWindow.qml"))); } diff --git a/src/mainview/MainView.qml b/src/mainview/MainView.qml index a757276c2..3d61b27b1 100644 --- a/src/mainview/MainView.qml +++ b/src/mainview/MainView.qml @@ -308,8 +308,8 @@ Window { mainViewWindowSidePanel.forceReselectConversationSmartListCurrentIndex() } - function onAccountStatusChanged() { - accountComboBox.resetAccountListModel() + function onAccountStatusChanged(accountId) { + accountComboBox.resetAccountListModel(accountId) } } diff --git a/src/mainview/components/AccountComboBox.qml b/src/mainview/components/AccountComboBox.qml index 53f4dff94..76f2beb88 100644 --- a/src/mainview/components/AccountComboBox.qml +++ b/src/mainview/components/AccountComboBox.qml @@ -31,7 +31,8 @@ ComboBox { signal settingBtnClicked // Reset accountListModel. - function resetAccountListModel() { + function resetAccountListModel(accountId) { + accountListModel.updateAvatarUid(accountId) accountListModel.reset() } @@ -39,9 +40,11 @@ ComboBox { target: accountListModel function onModelReset() { - userImageRoot.source = "data:image/png;base64," + accountListModel.data( - accountListModel.index(0, 0), AccountListModel.Picture) - currentAccountPresenceIndicator.status = + userImageRoot.updateImage( + AccountAdapter.currentAccountId, + accountListModel.data( + accountListModel.index(0, 0), AccountListModel.PictureUid)) + userImageRoot.presenceStatus = accountListModel.data(accountListModel.index(0, 0), AccountListModel.Status) textMetricsUserAliasRoot.text = accountListModel.data(accountListModel.index(0,0), AccountListModel.Alias) @@ -50,34 +53,20 @@ ComboBox { } } - Image { + AvatarImage { id: userImageRoot anchors.left: root.left anchors.leftMargin: 16 anchors.verticalCenter: root.verticalCenter - width: 30 - height: 30 + width: 40 + height: 40 - fillMode: Image.PreserveAspectFit + imageId: AccountAdapter.currentAccountId - // Base 64 format - source: "data:image/png;base64," + accountListModel.data( - accountListModel.index(0, 0), AccountListModel.Picture) - mipmap: true - - PresenceIndicator { - id: currentAccountPresenceIndicator - - anchors.right: userImageRoot.right - anchors.rightMargin: -2 - anchors.bottom: userImageRoot.bottom - anchors.bottomMargin: -2 - - status: accountListModel.data(accountListModel.index(0, 0), - AccountListModel.Status) - } + presenceStatus: accountListModel.data(accountListModel.index(0, 0), + AccountListModel.Status) } Text { @@ -251,8 +240,6 @@ ComboBox { } } - - indicator: null // Overwrite the combo box pop up to add footer (for add accounts). diff --git a/src/mainview/components/AccountComboBoxPopup.qml b/src/mainview/components/AccountComboBoxPopup.qml index a40318cf0..70869f91e 100644 --- a/src/mainview/components/AccountComboBoxPopup.qml +++ b/src/mainview/components/AccountComboBoxPopup.qml @@ -45,42 +45,29 @@ Popup { contentItem: ListView { id: comboBoxPopupListView - // In list view, index is an interger. clip: true model: accountListModel implicitHeight: contentHeight delegate: ItemDelegate { - Image { + AvatarImage { id: userImage anchors.left: parent.left anchors.leftMargin: 10 anchors.verticalCenter: parent.verticalCenter - width: 30 - height: 30 - - fillMode: Image.PreserveAspectFit - mipmap: true - - // Role::Picture - source: { - var data = accountListModel.data(accountListModel.index(index, 0), - AccountListModel.Picture) - if (data === undefined) { - return "" - } - return "data:image/png;base64," + data - } + width: 40 + height: 40 - PresenceIndicator { - anchors.right: userImage.right - anchors.rightMargin: -2 - anchors.bottom: userImage.bottom - anchors.bottomMargin: -2 + presenceStatus: Status - status: Status + Component.onCompleted: { + return updateImage( + accountListModel.data( + accountListModel.index(index, 0), AccountListModel.ID), + accountListModel.data( + accountListModel.index(index, 0), AccountListModel.PictureUid)) } } diff --git a/src/mainview/components/AudioCallPage.qml b/src/mainview/components/AudioCallPage.qml index 56a7ed126..74508ac77 100644 --- a/src/mainview/components/AudioCallPage.qml +++ b/src/mainview/components/AudioCallPage.qml @@ -28,7 +28,6 @@ import "../../commoncomponents" Rectangle { id: audioCallPageRect - property string contactImgSource: "" property string bestName: "Best Name" property string bestId: "Best Id" @@ -37,8 +36,7 @@ Rectangle { signal showFullScreenReqested function updateUI(accountId, convUid) { - contactImgSource = "data:image/png;base64," + UtilsAdapter.getContactImageString( - accountId, convUid) + contactImage.updateImage(convUid) bestName = UtilsAdapter.getBestName(accountId, convUid) var id = UtilsAdapter.getBestId(accountId, convUid) @@ -162,7 +160,7 @@ Rectangle { ColumnLayout { id: audioCallPageRectColumnLayout - Image { + AvatarImage { id: contactImage Layout.alignment: Qt.AlignCenter @@ -170,9 +168,8 @@ Rectangle { Layout.preferredWidth: 100 Layout.preferredHeight: 100 - fillMode: Image.PreserveAspectFit - source: contactImgSource - asynchronous: true + mode: AvatarImage.Mode.FromConvUid + showPresenceIndicator: false } Text { diff --git a/src/mainview/components/ContactPicker.qml b/src/mainview/components/ContactPicker.qml index 92e669ee3..11db11f9e 100644 --- a/src/mainview/components/ContactPicker.qml +++ b/src/mainview/components/ContactPicker.qml @@ -125,8 +125,6 @@ Popup { } onAboutToShow: { - - // Reset the model on each show. contactPickerListView.model = ContactAdapter.getContactSelectableModel( type) diff --git a/src/mainview/components/ContactPickerItemDelegate.qml b/src/mainview/components/ContactPickerItemDelegate.qml index 531e97199..e9aa9b621 100644 --- a/src/mainview/components/ContactPickerItemDelegate.qml +++ b/src/mainview/components/ContactPickerItemDelegate.qml @@ -26,7 +26,7 @@ import "../../commoncomponents" ItemDelegate { id: contactPickerItemDelegate - Image { + AvatarImage { id: contactPickerContactImage anchors.left: parent.left @@ -36,9 +36,8 @@ ItemDelegate { width: 40 height: 40 - fillMode: Image.PreserveAspectFit - source: "data:image/png;base64," + Picture - mipmap: true + mode: AvatarImage.Mode.FromContactUri + imageId: URI } Rectangle { diff --git a/src/mainview/components/ConversationSmartListUserImage.qml b/src/mainview/components/ConversationSmartListUserImage.qml deleted file mode 100644 index 772816122..000000000 --- a/src/mainview/components/ConversationSmartListUserImage.qml +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2020 by Savoir-faire Linux - * Author: Mingrui Zhang <mingrui.zhang@savoirfairelinux.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ - -import QtQuick 2.14 -import QtQuick.Controls 2.14 -import QtQuick.Layouts 1.14 -import net.jami.Models 1.0 -import "../../commoncomponents" - -Image { - id: userImage - - width: 40 - height: 40 - - fillMode: Image.PreserveAspectFit - source: "data:image/png;base64," + Picture - mipmap: true - - PresenceIndicator { - anchors.right: userImage.right - anchors.bottom: userImage.bottom - - visible: Presence === undefined ? false : Presence - } - - Rectangle { - id: unreadMessageCountRect - - anchors.right: userImage.right - anchors.rightMargin: -2 - anchors.top: userImage.top - anchors.topMargin: -2 - - width: 14 - height: 14 - - visible: UnreadMessagesCount > 0 - - Text { - id: unreadMessageCounttext - - anchors.centerIn: unreadMessageCountRect - - text: UnreadMessagesCount > 9 ? "···" : UnreadMessagesCount - color: "white" - font.pointSize: JamiTheme.textFontSize - } - - radius: 30 - color: JamiTheme.notificationRed - } -} diff --git a/src/mainview/components/ConversationSmartListView.qml b/src/mainview/components/ConversationSmartListView.qml index 109f6918f..4c362da84 100644 --- a/src/mainview/components/ConversationSmartListView.qml +++ b/src/mainview/components/ConversationSmartListView.qml @@ -89,6 +89,8 @@ ListView { delegate: ConversationSmartListViewItemDelegate { id: smartListItemDelegate + + onUpdateContactAvatarUidRequested: root.model.updateContactAvatarUid(uid) } ScrollIndicator.vertical: ScrollIndicator {} diff --git a/src/mainview/components/ConversationSmartListViewItemDelegate.qml b/src/mainview/components/ConversationSmartListViewItemDelegate.qml index f2489e5a4..a7f081976 100644 --- a/src/mainview/components/ConversationSmartListViewItemDelegate.qml +++ b/src/mainview/components/ConversationSmartListViewItemDelegate.qml @@ -30,6 +30,8 @@ ItemDelegate { property int lastInteractionPreferredWidth: 80 + signal updateContactAvatarUidRequested(string uid) + function convUid() { return UID } @@ -76,14 +78,29 @@ ItemDelegate { } } - ConversationSmartListUserImage { + AvatarImage { id: conversationSmartListUserImage anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 16 - } + width: 40 + height: 40 + + mode: AvatarImage.Mode.FromContactUri + + showPresenceIndicator: Presence === undefined ? false : Presence + + unreadMessagesCount: UnreadMessagesCount + + Component.onCompleted: { + var contactUid = URI + if (ContactType === Profile.Type.TEMPORARY) + updateContactAvatarUidRequested(contactUid) + updateImage(contactUid, PictureUid) + } + } RowLayout { id: rowUsernameAndLastInteractionDate @@ -202,7 +219,7 @@ ItemDelegate { userProfile.aliasText = DisplayName userProfile.registeredNameText = DisplayID userProfile.idText = URI - userProfile.contactPicBase64 = Picture + userProfile.contactImageUid = UID smartListContextMenu.openMenu() } else if (mouse.button === Qt.LeftButton) { conversationSmartListView.currentIndex = -1 diff --git a/src/mainview/components/ParticipantOverlay.qml b/src/mainview/components/ParticipantOverlay.qml index c98faa52b..e952bc524 100644 --- a/src/mainview/components/ParticipantOverlay.qml +++ b/src/mainview/components/ParticipantOverlay.qml @@ -39,6 +39,7 @@ Rectangle { participantName.text = name } + // TODO: try to use AvatarImage as well function setAvatar(avatar) { if (avatar === "") { opacity = 0 diff --git a/src/mainview/components/UserInfoCallPage.qml b/src/mainview/components/UserInfoCallPage.qml index 2275bfd4b..98598f280 100644 --- a/src/mainview/components/UserInfoCallPage.qml +++ b/src/mainview/components/UserInfoCallPage.qml @@ -30,13 +30,11 @@ Rectangle { id: userInfoCallRect property int buttonPreferredSize: 48 - property string contactImgSource: "" property string bestName: "Best Name" property string bestId: "Best Id" function updateUI(accountId, convUid) { - contactImgSource = "data:image/png;base64," + UtilsAdapter.getContactImageString( - accountId, convUid) + contactImg.updateImage(convUid) bestName = UtilsAdapter.getBestName(accountId, convUid) var id = UtilsAdapter.getBestId(accountId, convUid) bestId = (bestName !== id) ? id : "" @@ -74,7 +72,7 @@ Rectangle { onClicked: mainViewWindow.showWelcomeView() } - Image { + AvatarImage { id: contactImg Layout.alignment: Qt.AlignCenter @@ -83,9 +81,8 @@ Rectangle { Layout.preferredWidth: 100 Layout.preferredHeight: 100 - fillMode: Image.PreserveAspectFit - source: contactImgSource - asynchronous: true + mode: AvatarImage.Mode.FromConvUid + showPresenceIndicator: false } Rectangle { diff --git a/src/mainview/components/UserProfile.qml b/src/mainview/components/UserProfile.qml index 4914d7eb0..31f6543cf 100644 --- a/src/mainview/components/UserProfile.qml +++ b/src/mainview/components/UserProfile.qml @@ -28,7 +28,7 @@ BaseDialog { id: root property string responsibleConvUid: "" - property string contactPicBase64: "" + property string contactImageUid: "" property string aliasText: "" property string registeredNameText: "" property string idText: "" @@ -53,17 +53,17 @@ BaseDialog { rowSpacing: 16 columnSpacing: 24 - Image { + AvatarImage { id: contactImage Layout.alignment: Qt.AlignRight - Layout.preferredWidth: 130 + Layout.preferredWidth: preferredImgSize sourceSize.width: preferredImgSize sourceSize.height: preferredImgSize - fillMode: Image.PreserveAspectFit - mipmap: true + mode: AvatarImage.Mode.FromConvUid + showPresenceIndicator: false } // Visible when user alias is not empty or equals to id. @@ -196,8 +196,5 @@ BaseDialog { contactQrImage.source = "image://qrImage/contact_" + responsibleConvUid } - onContactPicBase64Changed: { - if (contactPicBase64 !== "") - contactImage.source = "data:image/png;base64," + contactPicBase64 - } + onContactImageUidChanged: contactImage.updateImage(contactImageUid) } diff --git a/src/messagesadapter.cpp b/src/messagesadapter.cpp index f1e556b8a..da93bd504 100644 --- a/src/messagesadapter.cpp +++ b/src/messagesadapter.cpp @@ -450,14 +450,14 @@ MessagesAdapter::setConversationProfileData(const lrc::api::conversation::Info& auto& contact = accInfo->contactModel->getContact(contactUri); auto bestName = Utils::bestNameForConversation(convInfo, *convModel); setInvitation(contact.profileInfo.type == lrc::api::profile::Type::PENDING - || contact.profileInfo.type == lrc::api::profile::Type::TEMPORARY, + || contact.profileInfo.type == lrc::api::profile::Type::TEMPORARY, bestName, contactUri); if (!contact.profileInfo.avatar.isEmpty()) { setSenderImage(contactUri, contact.profileInfo.avatar); } else { - auto avatar = Utils::conversationPhoto(convInfo.uid, *accInfo, true); + auto avatar = Utils::contactPhoto(convInfo.participants[0]); QByteArray ba; QBuffer bu(&ba); avatar.save(&bu, "PNG"); diff --git a/src/pixbufmanipulator.cpp b/src/pixbufmanipulator.cpp deleted file mode 100644 index 4860e7221..000000000 --- a/src/pixbufmanipulator.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2015-2020 by Savoir-faire Linux - * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> - * Author: Anthony Léonard <anthony.leonard@savoirfairelinux.com> - * Author: Olivier Soldano <olivier.soldano@savoirfairelinux.com> - * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ - -#include "pixbufmanipulator.h" - -#include <QBuffer> -#include <QByteArray> -#include <QIODevice> -#include <QImage> -#include <QMetaType> -#include <QPainter> -#include <QSize> - -#include "globalinstances.h" - -#include <api/account.h> -#include <api/contact.h> -#include <api/contactmodel.h> -#include <api/conversation.h> - -#include "utils.h" -#undef interface - -QVariant -PixbufManipulator::personPhoto(const QByteArray& data, const QString& type) -{ - QImage avatar; - const bool ret = avatar.loadFromData(QByteArray::fromBase64(data), type.toLatin1()); - if (!ret) { - qDebug() << "vCard image loading failed"; - return QVariant(); - } - return QPixmap::fromImage(Utils::getCirclePhoto(avatar, avatar.size().width())); -} - -QVariant -PixbufManipulator::numberCategoryIcon(const QVariant& p, - const QSize& size, - bool displayPresence, - bool isPresent) -{ - Q_UNUSED(p) - Q_UNUSED(size) - Q_UNUSED(displayPresence) - Q_UNUSED(isPresent) - return QVariant(); -} - -QByteArray -PixbufManipulator::toByteArray(const QVariant& pxm) -{ - auto image = pxm.value<QImage>(); - QByteArray ba = Utils::QImageToByteArray(image); - return ba; -} - -QVariant -PixbufManipulator::userActionIcon(const UserActionElement& state) const -{ - Q_UNUSED(state) - return QVariant(); -} - -QVariant -PixbufManipulator::decorationRole(const QModelIndex& index) -{ - Q_UNUSED(index) - return QVariant(); -} - -QVariant -PixbufManipulator::decorationRole(const lrc::api::conversation::Info& conversationInfo, - const lrc::api::account::Info& accountInfo) -{ - QImage photo; - auto contacts = conversationInfo.participants; - if (contacts.empty()) { - return QVariant::fromValue(photo); - } - try { - /* - * Get first contact photo. - */ - auto contactUri = contacts.front(); - auto contactInfo = accountInfo.contactModel->getContact(contactUri); - auto contactPhoto = contactInfo.profileInfo.avatar; - auto bestName = Utils::bestNameForContact(contactInfo); - auto bestId = Utils::bestIdForContact(contactInfo); - if (accountInfo.profileInfo.type == lrc::api::profile::Type::SIP - && contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY) { - photo = Utils::fallbackAvatar(QString(), QString()); - } else if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY - && contactInfo.profileInfo.uri.isEmpty()) { - photo = Utils::fallbackAvatar(QString(), QString()); - } else if (!contactPhoto.isEmpty()) { - QByteArray byteArray = contactPhoto.toLocal8Bit(); - photo = personPhoto(byteArray, nullptr).value<QImage>(); - if (photo.isNull()) { - auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName; - photo = Utils::fallbackAvatar("ring:" + contactInfo.profileInfo.uri, avatarName); - } - } else { - auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName; - photo = Utils::fallbackAvatar("ring:" + contactInfo.profileInfo.uri, avatarName); - } - } catch (...) { - } - return QVariant::fromValue(Utils::scaleAndFrame(photo)); -} diff --git a/src/pixbufmanipulator.h b/src/pixbufmanipulator.h deleted file mode 100644 index 7788f6526..000000000 --- a/src/pixbufmanipulator.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2015-2020 by Savoir-faire Linux - * Author: Edric Ladent Milaret <edric.ladent-milaret@savoirfairelinux.com> - * - * 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 <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <QImage> - -#include <interfaces/pixmapmanipulatori.h> -#include <memory> - -Q_DECLARE_METATYPE(QImage); - -class Person; - -QByteArray QImageToByteArray(QImage image); - -class PixbufManipulator : public Interfaces::PixmapManipulatorI -{ -public: - QVariant personPhoto(const QByteArray& data, const QString& type = "PNG") override; - - /* - * TODO: the following methods return an empty QVariant/QByteArray. - */ - QVariant numberCategoryIcon(const QVariant& p, - const QSize& size, - bool displayPresence = false, - bool isPresent = false) override; - QByteArray toByteArray(const QVariant& pxm) override; - QVariant userActionIcon(const UserActionElement& state) const override; - QVariant decorationRole(const QModelIndex& index) override; - QVariant decorationRole(const lrc::api::conversation::Info& conversation, - const lrc::api::account::Info& accountInfo) override; -}; diff --git a/src/previewrenderer.cpp b/src/previewrenderer.cpp index d5c72a14c..92cd0f0cb 100644 --- a/src/previewrenderer.cpp +++ b/src/previewrenderer.cpp @@ -112,24 +112,6 @@ PhotoboothPreviewRender::PhotoboothPreviewRender(QQuickItem* parent) PhotoboothPreviewRender::~PhotoboothPreviewRender() {} -QImage -PhotoboothPreviewRender::takePhoto() -{ - if (auto previewImage = LRCInstance::renderer()->getPreviewFrame()) { - return previewImage->copy(); - } - return QImage(); -} - -QString -PhotoboothPreviewRender::takeCroppedPhotoToBase64(int size) -{ - auto image = Utils::cropImage(takePhoto()); - auto avatar = image.scaled(size, size, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - - return QString::fromLatin1(Utils::QImageToByteArray(avatar).toBase64().data()); -} - void PhotoboothPreviewRender::paint(QPainter* painter) { diff --git a/src/previewrenderer.h b/src/previewrenderer.h index 6a2a66868..55249b124 100644 --- a/src/previewrenderer.h +++ b/src/previewrenderer.h @@ -63,9 +63,6 @@ public: explicit PhotoboothPreviewRender(QQuickItem* parent = 0); virtual ~PhotoboothPreviewRender(); - QImage takePhoto(); - Q_INVOKABLE QString takeCroppedPhotoToBase64(int size); - signals: void hideBooth(); diff --git a/src/settingsadapter.cpp b/src/settingsadapter.cpp index 8b6213ac7..a704512f8 100644 --- a/src/settingsadapter.cpp +++ b/src/settingsadapter.cpp @@ -263,15 +263,6 @@ SettingsAdapter::getAccountBestName() return Utils::bestNameForAccount(LRCInstance::getCurrentAccountInfo()); } -QString -SettingsAdapter::getAvatarImage_Base64(int avatarSize) -{ - auto& accountInfo = LRCInstance::getCurrentAccountInfo(); - auto avatar = Utils::accountPhoto(accountInfo, {avatarSize, avatarSize}); - - return QString::fromLatin1(Utils::QImageToByteArray(avatar).toBase64().data()); -} - bool SettingsAdapter::getIsDefaultAvatar() { @@ -280,18 +271,10 @@ SettingsAdapter::getIsDefaultAvatar() return accountInfo.profileInfo.avatar.isEmpty(); } -bool -SettingsAdapter::setCurrAccAvatar(QString avatarImgBase64) -{ - QImage avatarImg; - const bool ret = avatarImg.loadFromData(QByteArray::fromBase64(avatarImgBase64.toLatin1())); - if (!ret) { - qDebug() << "Current avatar loading from base64 fail"; - return false; - } else { - LRCInstance::setCurrAccAvatar(QPixmap::fromImage(avatarImg)); - } - return true; +void +SettingsAdapter::setCurrAccAvatar(QVariant avatarImg) +{ + LRCInstance::setCurrAccAvatar(QPixmap::fromImage(avatarImg.value<QImage>())); } void diff --git a/src/settingsadapter.h b/src/settingsadapter.h index 78a5164ff..db621f598 100644 --- a/src/settingsadapter.h +++ b/src/settingsadapter.h @@ -94,9 +94,8 @@ public: Q_INVOKABLE QString getAccountBestName(); // getters and setters of avatar image - Q_INVOKABLE QString getAvatarImage_Base64(int avatarSize); Q_INVOKABLE bool getIsDefaultAvatar(); - Q_INVOKABLE bool setCurrAccAvatar(QString avatarImgBase64); + Q_INVOKABLE void setCurrAccAvatar(QVariant avatarImg); Q_INVOKABLE void clearCurrentAvatar(); /* diff --git a/src/settingsview/components/AccountProfile.qml b/src/settingsview/components/AccountProfile.qml index 0f8963709..41a8719e4 100644 --- a/src/settingsview/components/AccountProfile.qml +++ b/src/settingsview/components/AccountProfile.qml @@ -52,7 +52,7 @@ ColumnLayout { } function setAvatar() { - currentAccountAvatar.setAvatarPixmap(SettingsAdapter.getAvatarImage_Base64(currentAccountAvatar.boothWidth), SettingsAdapter.getIsDefaultAvatar()) + currentAccountAvatar.setAvatarImage() } function stopBooth() { @@ -79,14 +79,8 @@ ColumnLayout { Layout.fillWidth: true Layout.alignment: Qt.AlignCenter + saveToConfig: true boothWidth: 180 - - onImageAcquired: SettingsAdapter.setCurrAccAvatar(imgBase64) - - onImageCleared: { - SettingsAdapter.clearCurrentAvatar() - setAvatar() - } } MaterialLineEdit { diff --git a/src/settingsview/components/BannedContacts.qml b/src/settingsview/components/BannedContacts.qml index 6bce03f14..f40592f6f 100644 --- a/src/settingsview/components/BannedContacts.qml +++ b/src/settingsview/components/BannedContacts.qml @@ -137,7 +137,6 @@ ColumnLayout { contactName : ContactName contactID: ContactID - contactPicture_base64: ContactPicture onClicked: bannedListWidget.currentIndex = index diff --git a/src/settingsview/components/BannedItemDelegate.qml b/src/settingsview/components/BannedItemDelegate.qml index ca93d18fb..e20dc9215 100644 --- a/src/settingsview/components/BannedItemDelegate.qml +++ b/src/settingsview/components/BannedItemDelegate.qml @@ -31,12 +31,13 @@ ItemDelegate { property string contactName : "" property string contactID: "" - property string contactPicture_base64:"" signal btnReAddContactClicked highlighted: ListView.isCurrentItem + onContactIDChanged: avatarImg.updateImage(contactID) + RowLayout { anchors.fill: parent @@ -52,11 +53,14 @@ ItemDelegate { background: Rectangle { anchors.fill: parent color: "transparent" - Image { + AvatarImage { id: avatarImg anchors.fill: parent - source: contactPicture_base64 === "" ? "" : "data:image/png;base64," + contactPicture_base64 + + mode: AvatarImage.Mode.FromContactUri + showPresenceIndicator: false + fillMode: Image.PreserveAspectCrop layer.enabled: true layer.effect: OpacityMask { diff --git a/src/smartlistmodel.cpp b/src/smartlistmodel.cpp index 3fd0b47f2..439a6c1f8 100644 --- a/src/smartlistmodel.cpp +++ b/src/smartlistmodel.cpp @@ -21,12 +21,8 @@ #include "smartlistmodel.h" #include "lrcinstance.h" -#include "pixbufmanipulator.h" #include "utils.h" -#include "api/contactmodel.h" -#include "globalinstances.h" - #include <QDateTime> SmartListModel::SmartListModel(QObject* parent, @@ -148,7 +144,6 @@ SmartListModel::roleNames() const QHash<int, QByteArray> roles; roles[DisplayName] = "DisplayName"; roles[DisplayID] = "DisplayID"; - roles[Picture] = "Picture"; roles[Presence] = "Presence"; roles[URI] = "URI"; roles[UnreadMessagesCount] = "UnreadMessagesCount"; @@ -163,6 +158,7 @@ SmartListModel::roleNames() const roles[SectionName] = "SectionName"; roles[AccountId] = "AccountId"; roles[Draft] = "Draft"; + roles[PictureUid] = "PictureUid"; return roles; } @@ -183,6 +179,8 @@ void SmartListModel::fillConversationsList() { beginResetModel(); + fillContactAvatarUidMap(LRCInstance::getCurrentAccountInfo().contactModel->getAllContacts()); + auto* convModel = LRCInstance::getCurrentConversationModel(); conversations_.clear(); @@ -208,6 +206,39 @@ SmartListModel::updateConversation(const QString& convUid) } } +void +SmartListModel::updateContactAvatarUid(const QString& contactUri) +{ + contactAvatarUidMap_[contactUri] = Utils::generateUid(); +} + +void +SmartListModel::fillContactAvatarUidMap(const ContactModel::ContactInfoMap& contacts) +{ + if (contacts.size() == 0) { + contactAvatarUidMap_.clear(); + return; + } + + if (contactAvatarUidMap_.isEmpty() || contacts.size() != contactAvatarUidMap_.size()) { + bool useContacts = contacts.size() > contactAvatarUidMap_.size(); + auto contactsKeyList = contacts.keys(); + auto contactAvatarUidMapKeyList = contactAvatarUidMap_.keys(); + + for (int i = 0; + i < (useContacts ? contactsKeyList.size() : contactAvatarUidMapKeyList.size()); + ++i) { + // Insert or update + if (i < contactsKeyList.size() && !contactAvatarUidMap_.contains(contactsKeyList.at(i))) + contactAvatarUidMap_.insert(contactsKeyList.at(i), Utils::generateUid()); + // Remove + if (i < contactAvatarUidMapKeyList.size() + && !contacts.contains(contactAvatarUidMapKeyList.at(i))) + contactAvatarUidMap_.remove(contactAvatarUidMapKeyList.at(i)); + } + } +} + void SmartListModel::toggleSection(const QString& section) { @@ -241,12 +272,10 @@ SmartListModel::getConversationItemData(const conversation::Info& item, return QVariant(); } auto& contactModel = accountInfo.contactModel; + + // Since we are using image provider right now, image url representation should be unique to + // be able to use the image cache, account avatar will only be updated once PictureUid changed switch (role) { - case Role::Picture: { - auto contactImage - = GlobalInstances::pixmapManipulator().decorationRole(item, accountInfo).value<QImage>(); - return QString::fromLatin1(Utils::QImageToByteArray(contactImage).toBase64().data()); - } case Role::DisplayName: { if (!item.participants.isEmpty()) { auto& contact = contactModel->getContact(item.participants[0]); @@ -268,10 +297,15 @@ SmartListModel::getConversationItemData(const conversation::Info& item, } return QVariant(false); } + case Role::PictureUid: { + if (!item.participants.isEmpty()) { + return QVariant(contactAvatarUidMap_[item.participants[0]]); + } + return QVariant(""); + } case Role::URI: { if (!item.participants.isEmpty()) { - auto& contact = contactModel->getContact(item.participants[0]); - return QVariant(contact.profileInfo.uri); + return QVariant(item.participants[0]); } return QVariant(""); } @@ -331,13 +365,13 @@ SmartListModel::getConversationItemData(const conversation::Info& item, if (!convInfo.uid.isEmpty()) { auto* callModel = LRCInstance::getCurrentCallModel(); const auto call = callModel->getCall(convInfo.callId); - return QVariant(callModel->hasCall(convInfo.callId) - && ((!call.isOutgoing - && (call.status == lrc::api::call::Status::IN_PROGRESS - || call.status == lrc::api::call::Status::PAUSED - || call.status == lrc::api::call::Status::INCOMING_RINGING)) - || (call.isOutgoing - && call.status != lrc::api::call::Status::ENDED))); + return QVariant( + callModel->hasCall(convInfo.callId) + && ((!call.isOutgoing + && (call.status == lrc::api::call::Status::IN_PROGRESS + || call.status == lrc::api::call::Status::PAUSED + || call.status == lrc::api::call::Status::INCOMING_RINGING)) + || (call.isOutgoing && call.status != lrc::api::call::Status::ENDED))); } return QVariant(false); } diff --git a/src/smartlistmodel.h b/src/smartlistmodel.h index 9bb56900c..ccf7a0fea 100644 --- a/src/smartlistmodel.h +++ b/src/smartlistmodel.h @@ -24,6 +24,7 @@ #include "api/contact.h" #include "api/conversation.h" #include "api/conversationmodel.h" +#include "api/contactmodel.h" #include <QAbstractItemModel> @@ -42,7 +43,6 @@ public: enum Role { DisplayName = Qt::UserRole + 1, DisplayID, - Picture, Presence, URI, UnreadMessagesCount, @@ -58,6 +58,7 @@ public: CallState, SectionName, AccountId, + PictureUid, Draft }; Q_ENUM(Role) @@ -85,15 +86,28 @@ public: Q_INVOKABLE void fillConversationsList(); Q_INVOKABLE void updateConversation(const QString& conv); + /* + * This function is to update contact avatar uuid for current account when there's an contact + * avatar changed. + */ + Q_INVOKABLE void updateContactAvatarUid(const QString& contactUri); + private: QVariant getConversationItemData(const ConversationInfo& item, const AccountInfo& accountInfo, int role) const; + + /* + * Give a uuid for each contact avatar for current account and it will serve PictureUid role + */ + void fillContactAvatarUidMap(const ContactModel::ContactInfoMap& contacts); + /* * List sectioning. */ Type listModelType_; QMap<QString, bool> sectionState_; QMap<ConferenceableItem, ConferenceableValue> conferenceables_; + QMap<QString, QString> contactAvatarUidMap_; ConversationModel::ConversationQueue conversations_; }; diff --git a/src/utils.cpp b/src/utils.cpp index b215d2360..b2d066418 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -25,9 +25,7 @@ #include "globalsystemtray.h" #include "jamiavatartheme.h" #include "lrcinstance.h" -#include "pixbufmanipulator.h" -#include <globalinstances.h> #include <qrencode.h> #include <QApplication> @@ -43,6 +41,7 @@ #include <QSvgRenderer> #include <QTranslator> #include <QtConcurrent/QtConcurrent> +#include <QUuid> #ifdef Q_OS_WIN #include <lmcons.h> @@ -245,14 +244,52 @@ Utils::GetISODate() #endif } -QString -Utils::getContactImageString(const QString& accountId, const QString& uid) +QImage +Utils::contactPhoto(const QString& contactUri, const QSize& size) +{ + QImage photo; + + try { + /* + * Get first contact photo. + */ + auto& accountInfo = LRCInstance::accountModel().getAccountInfo(LRCInstance::getCurrAccId()); + auto contactInfo = accountInfo.contactModel->getContact(contactUri); + auto contactPhoto = contactInfo.profileInfo.avatar; + auto bestName = Utils::bestNameForContact(contactInfo); + auto bestId = Utils::bestIdForContact(contactInfo); + if (accountInfo.profileInfo.type == lrc::api::profile::Type::SIP + && contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY) { + photo = Utils::fallbackAvatar(QString(), QString()); + } else if (contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY + && contactInfo.profileInfo.uri.isEmpty()) { + photo = Utils::fallbackAvatar(QString(), QString()); + } else if (!contactPhoto.isEmpty()) { + QByteArray byteArray = contactPhoto.toLocal8Bit(); + photo = contactPhotoFromBase64(byteArray, nullptr); + if (photo.isNull()) { + auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName; + photo = Utils::fallbackAvatar("ring:" + contactInfo.profileInfo.uri, avatarName); + } + } else { + auto avatarName = contactInfo.profileInfo.uri == bestName ? QString() : bestName; + photo = Utils::fallbackAvatar("ring:" + contactInfo.profileInfo.uri, avatarName); + } + } catch (...) { + } + return Utils::scaleAndFrame(photo, size); +} + +QImage +Utils::contactPhotoFromBase64(const QByteArray& data, const QString& type) { - return QString::fromLatin1( - Utils::QImageToByteArray( - Utils::conversationPhoto(uid, LRCInstance::getAccountInfo(accountId))) - .toBase64() - .data()); + QImage avatar; + const bool ret = avatar.loadFromData(QByteArray::fromBase64(data), type.toLatin1()); + if (!ret) { + qDebug() << "Utils: vCard image loading failed"; + return QImage(); + } + return Utils::getCirclePhoto(avatar, avatar.size().width()); } QImage @@ -549,21 +586,6 @@ Utils::getReplyMessageBox(QWidget* widget, const QString& title, const QString& return false; } -QImage -Utils::conversationPhoto(const QString& convUid, - const lrc::api::account::Info& accountInfo, - bool filtered) -{ - auto* convModel = LRCInstance::getCurrentConversationModel(); - const auto convInfo = convModel->getConversationForUID(convUid); - if (!convInfo.uid.isEmpty()) { - return GlobalInstances::pixmapManipulator() - .decorationRole(convInfo, accountInfo) - .value<QImage>(); - } - return QImage(); -} - QColor Utils::getAvatarColor(const QString& canonicalUri) { @@ -587,10 +609,12 @@ Utils::getAvatarColor(const QString& canonicalUri) QImage Utils::fallbackAvatar(const QString& canonicalUriStr, const QString& letterStr, const QSize& size) { + auto sizeToUse = size.height() >= defaultAvatarSize.height() ? size : defaultAvatarSize; + /* * We start with a transparent avatar. */ - QImage avatar(size, QImage::Format_ARGB32); + QImage avatar(sizeToUse, QImage::Format_ARGB32); avatar.fill(Qt::transparent); /* @@ -651,7 +675,7 @@ Utils::fallbackAvatar(const QString& canonicalUriStr, const QString& letterStr, painter.drawPixmap(overlayRect, QPixmap(":/images/default_avatar_overlay.svg")); } - return avatar; + return avatar.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QImage @@ -802,7 +826,7 @@ Utils::accountPhoto(const lrc::api::account::Info& accountInfo, const QSize& siz QImage photo; if (!accountInfo.profileInfo.avatar.isEmpty()) { QByteArray ba = accountInfo.profileInfo.avatar.toLocal8Bit(); - photo = GlobalInstances::pixmapManipulator().personPhoto(ba, nullptr).value<QImage>(); + photo = contactPhotoFromBase64(ba, nullptr); } else { auto bestId = bestIdForAccount(accountInfo); auto bestName = bestNameForAccount(accountInfo); @@ -843,3 +867,9 @@ Utils::isImage(const QString& fileExt) return true; return false; } + +QString +Utils::generateUid() +{ + return QUuid::createUuid().toString(); +} diff --git a/src/utils.h b/src/utils.h index ccecb8752..2e74d0315 100644 --- a/src/utils.h +++ b/src/utils.h @@ -100,11 +100,9 @@ bool getReplyMessageBox(QWidget* widget, const QString& title, const QString& te * Image manipulation */ static const QSize defaultAvatarSize {128, 128}; -QString getContactImageString(const QString& accountId, const QString& uid); +QImage contactPhotoFromBase64(const QByteArray& data, const QString& type); +QImage contactPhoto(const QString& contactUri, const QSize& size = defaultAvatarSize); QImage getCirclePhoto(const QImage original, int sizePhoto); -QImage conversationPhoto(const QString& convUid, - const lrc::api::account::Info& accountInfo, - bool filtered = false); QColor getAvatarColor(const QString& canonicalUri); QImage fallbackAvatar(const QString& canonicalUriStr, const QString& letterStr = QString(), @@ -123,6 +121,7 @@ QImage cropImage(const QImage& img); QPixmap pixmapFromSvg(const QString& svg_resource, const QSize& size); QImage setupQRCode(QString ringID, int margin); bool isImage(const QString& fileExt); +QString generateUid(); /* * Misc diff --git a/src/utilsadapter.cpp b/src/utilsadapter.cpp index d69d7049d..137d4dd5e 100644 --- a/src/utilsadapter.cpp +++ b/src/utilsadapter.cpp @@ -105,12 +105,6 @@ UtilsAdapter::checkStartupLink() return Utils::CheckStartupLink(L"Jami"); } -const QString -UtilsAdapter::getContactImageString(const QString& accountId, const QString& uid) -{ - return Utils::getContactImageString(accountId, uid); -} - const QString UtilsAdapter::getBestName(const QString& accountId, const QString& uid) { @@ -356,17 +350,6 @@ UtilsAdapter::getAbsPath(QString path) #endif } -QString -UtilsAdapter::getCroppedImageBase64FromFile(QString fileName, int size) -{ - auto image = Utils::cropImage(QImage(fileName)); - auto croppedImage = image.scaled(size, - size, - Qt::KeepAspectRatioByExpanding, - Qt::SmoothTransformation); - return QString::fromLatin1(Utils::QImageToByteArray(croppedImage).toBase64().data()); -} - bool UtilsAdapter::checkShowPluginsButton() { diff --git a/src/utilsadapter.h b/src/utilsadapter.h index 9a30e12cf..36e0658c7 100644 --- a/src/utilsadapter.h +++ b/src/utilsadapter.h @@ -44,7 +44,6 @@ public: Q_INVOKABLE bool createStartupLink(); Q_INVOKABLE QString GetRingtonePath(); Q_INVOKABLE bool checkStartupLink(); - Q_INVOKABLE const QString getContactImageString(const QString& accountId, const QString& uid); Q_INVOKABLE void removeConversation(const QString& accountId, const QString& uid, bool banContact = false); @@ -77,7 +76,6 @@ public: Q_INVOKABLE QString toFileInfoName(QString inputFileName); Q_INVOKABLE QString toFileAbsolutepath(QString inputFileName); Q_INVOKABLE QString getAbsPath(QString path); - Q_INVOKABLE QString getCroppedImageBase64FromFile(QString fileName, int size); Q_INVOKABLE bool checkShowPluginsButton(); Q_INVOKABLE QString fileName(const QString& path); Q_INVOKABLE QString getExt(const QString& path); diff --git a/src/wizardview/WizardView.qml b/src/wizardview/WizardView.qml index 154b7bd3a..e836c060d 100644 --- a/src/wizardview/WizardView.qml +++ b/src/wizardview/WizardView.qml @@ -385,14 +385,13 @@ Rectangle { } onSaveProfile: { - SettingsAdapter.setCurrAccAvatar(profilePage.boothImgBase64) + if (profilePage.profileImg) + SettingsAdapter.setCurrAccAvatar(profilePage.profileImg) AccountAdapter.setCurrAccDisplayName(profilePage.displayName) leave() } - onLeavePage: { - leave() - } + onLeavePage: leave() } } } diff --git a/src/wizardview/components/CreateSIPAccountPage.qml b/src/wizardview/components/CreateSIPAccountPage.qml index 2e7f8570c..f077aebfe 100644 --- a/src/wizardview/components/CreateSIPAccountPage.qml +++ b/src/wizardview/components/CreateSIPAccountPage.qml @@ -32,8 +32,6 @@ Rectangle { property alias text_sipPasswordEditAlias: sipPasswordEdit.text property int preferredHeight: createSIPAccountPageColumnLayout.implicitHeight - property var boothImgBase64: null - signal createAccount signal leavePage diff --git a/src/wizardview/components/ProfilePage.qml b/src/wizardview/components/ProfilePage.qml index 612b4f460..adc7ac5d3 100644 --- a/src/wizardview/components/ProfilePage.qml +++ b/src/wizardview/components/ProfilePage.qml @@ -26,11 +26,13 @@ import "../../commoncomponents" Rectangle { id: root + property alias profileImg: setAvatarWidget.boothImg property int preferredHeight: profilePageColumnLayout.implicitHeight function initializeOnShowUp() { + setAvatarWidget.hasAvatar = false + setAvatarWidget.setAvatarImage(AvatarImage.Mode.Default, "") clearAllTextFields() - boothImgBase64 = "" saveProfileBtn.spinnerTriggered = true } @@ -48,7 +50,6 @@ Rectangle { signal saveProfile property var showBottom: false - property alias boothImgBase64: setAvatarWidget.imgBase64 property alias displayName: aliasEdit.text property bool isRdv: false -- GitLab