mirror of
https://git.jami.net/savoirfairelinux/jami-client-qt.git
synced 2025-07-14 04:25:22 +02:00
migration: use image provider to show avatar image
1. Use avatarimageprovider 2. Remove redundant base64 code Change-Id: I2a2517890e95b4a9f9a363fbea2251d6d5dd1c8f
This commit is contained in:
parent
b4b56aec4b
commit
173cf2be50
50 changed files with 587 additions and 562 deletions
|
@ -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
|
||||
|
|
|
@ -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 \
|
||||
|
|
2
qml.qrc
2
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>
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 .
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
71
src/avatarimageprovider.h
Normal file
71
src/avatarimageprovider.h
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
183
src/commoncomponents/AvatarImage.qml
Normal file
183
src/commoncomponents/AvatarImage.qml
Normal file
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
|
|
|
@ -308,8 +308,8 @@ Window {
|
|||
mainViewWindowSidePanel.forceReselectConversationSmartListCurrentIndex()
|
||||
}
|
||||
|
||||
function onAccountStatusChanged() {
|
||||
accountComboBox.resetAccountListModel()
|
||||
function onAccountStatusChanged(accountId) {
|
||||
accountComboBox.resetAccountListModel(accountId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
mipmap: true
|
||||
presenceStatus: Status
|
||||
|
||||
// Role::Picture
|
||||
source: {
|
||||
var data = accountListModel.data(accountListModel.index(index, 0),
|
||||
AccountListModel.Picture)
|
||||
if (data === undefined) {
|
||||
return ""
|
||||
}
|
||||
return "data:image/png;base64," + data
|
||||
}
|
||||
|
||||
PresenceIndicator {
|
||||
anchors.right: userImage.right
|
||||
anchors.rightMargin: -2
|
||||
anchors.bottom: userImage.bottom
|
||||
anchors.bottomMargin: -2
|
||||
|
||||
status: Status
|
||||
Component.onCompleted: {
|
||||
return updateImage(
|
||||
accountListModel.data(
|
||||
accountListModel.index(index, 0), AccountListModel.ID),
|
||||
accountListModel.data(
|
||||
accountListModel.index(index, 0), AccountListModel.PictureUid))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -125,8 +125,6 @@ Popup {
|
|||
}
|
||||
|
||||
onAboutToShow: {
|
||||
|
||||
|
||||
// Reset the model on each show.
|
||||
contactPickerListView.model = ContactAdapter.getContactSelectableModel(
|
||||
type)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -89,6 +89,8 @@ ListView {
|
|||
|
||||
delegate: ConversationSmartListViewItemDelegate {
|
||||
id: smartListItemDelegate
|
||||
|
||||
onUpdateContactAvatarUidRequested: root.model.updateContactAvatarUid(uid)
|
||||
}
|
||||
|
||||
ScrollIndicator.vertical: ScrollIndicator {}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -39,6 +39,7 @@ Rectangle {
|
|||
participantName.text = name
|
||||
}
|
||||
|
||||
// TODO: try to use AvatarImage as well
|
||||
function setAvatar(avatar) {
|
||||
if (avatar === "") {
|
||||
opacity = 0
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -63,9 +63,6 @@ public:
|
|||
explicit PhotoboothPreviewRender(QQuickItem* parent = 0);
|
||||
virtual ~PhotoboothPreviewRender();
|
||||
|
||||
QImage takePhoto();
|
||||
Q_INVOKABLE QString takeCroppedPhotoToBase64(int size);
|
||||
|
||||
signals:
|
||||
void hideBooth();
|
||||
|
||||
|
|
|
@ -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)
|
||||
void
|
||||
SettingsAdapter::setCurrAccAvatar(QVariant avatarImg)
|
||||
{
|
||||
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;
|
||||
LRCInstance::setCurrAccAvatar(QPixmap::fromImage(avatarImg.value<QImage>()));
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -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();
|
||||
|
||||
/*
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -137,7 +137,6 @@ ColumnLayout {
|
|||
|
||||
contactName : ContactName
|
||||
contactID: ContactID
|
||||
contactPicture_base64: ContactPicture
|
||||
|
||||
onClicked: bannedListWidget.currentIndex = index
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
return QString::fromLatin1(
|
||||
Utils::QImageToByteArray(
|
||||
Utils::conversationPhoto(uid, LRCInstance::getAccountInfo(accountId)))
|
||||
.toBase64()
|
||||
.data());
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,8 +32,6 @@ Rectangle {
|
|||
property alias text_sipPasswordEditAlias: sipPasswordEdit.text
|
||||
property int preferredHeight: createSIPAccountPageColumnLayout.implicitHeight
|
||||
|
||||
property var boothImgBase64: null
|
||||
|
||||
signal createAccount
|
||||
signal leavePage
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue