1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-07-06 00:25:26 +02:00

swarm creation: add ability to change avatar

PhotoBoothView has a new variable to be used during Swarm's creation
This update an image in the cache and is used in the profile of the
conversation.
Also, add top bar for NewSwarmPage

Change-Id: I156c9cffb85e15b7c041bcf16b1501851470e8a5
GitLab: #670
This commit is contained in:
Sébastien Blin 2022-02-11 16:25:42 -05:00
parent 4a581d0a1a
commit a5cfffef6d
13 changed files with 278 additions and 30 deletions

View file

@ -60,9 +60,12 @@ public:
}
auto type = idInfo.at(0);
if (type == "conversation")
if (type == "conversation") {
if (imageId == "temp")
return Utils::tempConversationAvatar(requestedSize);
return Utils::conversationAvatar(lrcInstance_, imageId, requestedSize);
else if (type == "account")
} else if (type == "account")
return Utils::accountPhoto(lrcInstance_, imageId, requestedSize);
else if (type == "contact")
return Utils::contactPhoto(lrcInstance_, imageId, requestedSize);

View file

@ -35,6 +35,10 @@ AvatarRegistry::AvatarRegistry(LRCInstance* instance, QObject* parent)
&AvatarRegistry::addOrUpdateImage,
Qt::UniqueConnection);
connect(lrcInstance_, &LRCInstance::base64SwarmAvatarChanged, this, [&] {
addOrUpdateImage("temp");
});
if (!lrcInstance_->get_currentAccountId().isEmpty())
connectAccount();
}
@ -62,6 +66,12 @@ AvatarRegistry::connectAccount()
this,
&AvatarRegistry::onProfileUpdated,
Qt::UniqueConnection);
connect(lrcInstance_->getCurrentConversationModel(),
&ConversationModel::conversationUpdated,
this,
&AvatarRegistry::addOrUpdateImage,
Qt::UniqueConnection);
}
void

View file

@ -30,6 +30,7 @@ Item {
property bool isPreviewing: false
property alias imageId: avatar.imageId
property bool newConversation: false
property real avatarSize
signal focusOnPreviousItem
@ -94,7 +95,10 @@ Item {
}
var filePath = UtilsAdapter.getAbsPath(file)
AccountAdapter.setCurrentAccountAvatarFile(filePath)
if (!root.newConversation)
AccountAdapter.setCurrentAccountAvatarFile(filePath)
else
UtilsAdapter.setSwarmCreationImageFromFile(filePath, root.imageId)
}
onRejected: {
@ -125,6 +129,8 @@ Item {
visible: !preview.visible
mode: newConversation? Avatar.Mode.Conversation : Avatar.Mode.Account
fillMode: Image.PreserveAspectCrop
showPresenceIndicator: false
}
@ -220,8 +226,11 @@ Item {
onClicked: {
if (isPreviewing) {
flashAnimation.start()
AccountAdapter.setCurrentAccountAvatarBase64(
preview.takePhoto(avatarSize))
var photo = preview.takePhoto(avatarSize)
if (!root.newConversation)
AccountAdapter.setCurrentAccountAvatarBase64(photo)
else
UtilsAdapter.setSwarmCreationImageFromString(photo, imageId)
stopBooth()
return
}
@ -237,7 +246,15 @@ Item {
Layout.alignment: Qt.AlignHCenter
visible: isPreviewing || LRCInstance.currentAccountAvatarSet
visible: {
if (isPreviewing)
return true
if (!newConversation && LRCInstance.currentAccountAvatarSet)
return true
if (newConversation && UtilsAdapter.swarmCreationImage(imageId).length !== 0)
return true
return false
}
radius: JamiTheme.primaryRadius
source: JamiResources.round_close_24dp_svg
@ -265,8 +282,12 @@ Item {
onClicked: {
stopBooth()
if (!isPreviewing)
AccountAdapter.setCurrentAccountAvatarBase64()
if (!isPreviewing) {
if (!root.newConversation)
AccountAdapter.setCurrentAccountAvatarBase64()
else
UtilsAdapter.setSwarmCreationImageFromString("", imageId)
}
}
}

View file

@ -629,4 +629,5 @@ Item {
property string kickMember: qsTr("Kick member")
property string administrator: qsTr("Administrator")
property string invited: qsTr("Invited")
property string removeMember: qsTr("Remove member")
}

View file

@ -133,6 +133,7 @@ Q_SIGNALS:
void quitEngineRequested();
void conversationUpdated(const QString& convId, const QString& accountId);
void draftSaved(const QString& convId);
void base64SwarmAvatarChanged();
private:
std::unique_ptr<Lrc> lrc_;

View file

@ -376,6 +376,10 @@ Rectangle {
pushNewSwarmPage()
}
}
onHighlightedMembersChanged: {
newSwarmPage.members = mainViewSidePanel.highlightedMembers
}
}
CallStackView {
@ -426,6 +430,10 @@ Rectangle {
mainViewSidePanel.showSwarmListView(newSwarmPage.visible)
}
onRemoveMember: function(convId, member) {
mainViewSidePanel.removeMember(convId, member)
}
onCreateSwarmClicked: function(title, description, avatar) {
ConversationsAdapter.createSwarm(title, description, avatar, mainViewSidePanel.highlightedMembers)
backToMainView()

View file

@ -33,12 +33,95 @@ Rectangle {
color: JamiTheme.chatviewBgColor
signal createSwarmClicked(string title, string description, string avatar)
signal removeMember(string convId, string member)
onVisibleChanged: {
UtilsAdapter.setSwarmCreationImageFromString()
}
property var members: []
RowLayout {
id: labelsMember
anchors.top: root.top
anchors.topMargin: 16
anchors.leftMargin: 16
Layout.preferredWidth: root.width
spacing: 16
Label {
text: qsTr("To:")
font.bold: true
color: JamiTheme.textColor
}
ScrollView {
Layout.preferredWidth: root.width
Layout.fillWidth: true
Layout.preferredHeight: 48
Layout.topMargin: 16
clip: true
RowLayout {
anchors.fill: parent
Repeater {
id: repeater
delegate: Rectangle {
id: delegate
radius: (delegate.height + 12) / 2
width: childrenRect.width + 12
height: childrenRect.height + 12
RowLayout {
anchors.centerIn: parent
Label {
text: UtilsAdapter.getBestNameForUri(CurrentAccount.id, modelData.uri)
color: JamiTheme.textColor
}
PushButton {
id: removeUserBtn
Layout.leftMargin: 8
preferredSize: 24
source: JamiResources.round_close_24dp_svg
toolTipText: JamiStrings.removeMember
normalColor: "transparent"
imageColor: "transparent"
onClicked: root.removeMember(modelData.convId, modelData.uri)
}
}
color: "grey"
}
model: root.members
}
}
}
}
ColumnLayout {
id: mainLayout
anchors.centerIn: root
PhotoboothView {
id: currentAccountAvatar
Layout.alignment: Qt.AlignCenter
newConversation: true
imageId: root.visible ? "temp" : ""
avatarSize: 180
}
EditableLineEdit {
id: title
Layout.alignment: Qt.AlignCenter
@ -83,7 +166,7 @@ Rectangle {
text: JamiStrings.createTheSwarm
onClicked: {
createSwarmClicked(title.text, description.text, "")
createSwarmClicked(title.text, description.text, UtilsAdapter.swarmCreationImage())
}
}
}

View file

@ -62,20 +62,66 @@ Rectangle {
property var highlighted: []
property var highlightedMembers: []
function refreshHighlighted() {
var result = []
for (var idx in highlighted) {
var convId = highlighted[idx]
function refreshHighlighted(convId, highlightedStatus) {
var newH = root.highlighted
var newHm = root.highlightedMembers
if (highlightedStatus) {
var item = ConversationsAdapter.getConvInfoMap(convId)
var added = false
for (var idx in item.uris) {
var uri = item.uris[idx]
if (!result.indexOf(uri) != -1 && uri != CurrentAccount.uri) {
result.push(uri)
if (!Array.from(newHm).find(r => r.uri === uri) && uri != CurrentAccount.uri) {
newHm.push({"uri": uri, "convId": convId})
added = true
}
}
if (!added)
return false
} else {
newH = Array.from(newH).filter(r => r !== convId)
newHm = Array.from(newHm).filter(r => r.convId !== convId)
}
highlightedMembers = result
// We can't have more than 8 participants yet.
if (newHm.length > 8) {
return false
}
newH.push(convId)
root.highlighted = newH
root.highlightedMembers = newHm
ConversationsAdapter.ignoreFiltering(root.highlighted)
return true
}
function clearHighlighted() {
root.highlighted = []
root.highlightedMembers = []
}
function removeMember(convId, member) {
var refreshHighlighted = true
var newHm = []
for (var hm in root.highlightedMembers) {
var m = root.highlightedMembers[hm]
if (m.convId == convId && m.uri == member) {
continue;
} else if (m.convId == convId) {
refreshHighlighted = false
}
newHm.push(m)
}
root.highlightedMembers = newHm
if (refreshHighlighted) {
// Remove highlighted status if necessary
for (var d in swarmCurrentConversationList.contentItem.children) {
var delegate = swarmCurrentConversationList.contentItem.children[d]
if (delegate.convId == convId)
delegate.highlighted = false
}
}
}
function showSwarmListView(v) {
@ -280,23 +326,21 @@ Rectangle {
onVisibleChanged: {
if (!visible) {
highlighted = false
root.refreshHighlighted()
root.clearHighlighted()
}
}
onHighlightedChanged: function onHighlightedChanged() {
var currentHighlighted = root.highlighted
if (!root.refreshHighlighted(convId, highlighted)) {
highlighted = false
return
}
if (highlighted) {
root.highlighted.push(convId)
} else {
root.highlighted = Array.from(root.highlighted).filter(r => r !== convId)
}
root.refreshHighlighted()
// We can't have more than 8 participants yet.
if (root.highlightedMembers.length > 8) {
highlighted = false
root.refreshHighlighted()
}
}
}
currentIndex: model.currentFilteredRow

View file

@ -42,18 +42,16 @@ Rectangle {
Layout.fillWidth: true
spacing: 0
ConversationAvatar {
id: conversationAvatar
PhotoboothView {
id: currentAccountAvatar
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: JamiTheme.avatarSizeInCall
Layout.preferredHeight: JamiTheme.avatarSizeInCall
Layout.topMargin: JamiTheme.swarmDetailsPageTopMargin
Layout.bottomMargin: JamiTheme.preferredMarginSize
newConversation: true
imageId: LRCInstance.selectedConvUid
showPresenceIndicator: false
avatarSize: JamiTheme.avatarSizeInCall
}
EditableLineEdit {

View file

@ -395,6 +395,10 @@ Utils::conversationAvatar(LRCInstance* instance,
auto& accInfo = instance->accountModel().getAccountInfo(
accountId.isEmpty() ? instance->get_currentAccountId() : accountId);
auto* convModel = accInfo.conversationModel.get();
auto avatarb64 = convModel->avatar(convId);
if (!avatarb64.isEmpty())
return scaleAndFrame(imageFromBase64String(avatarb64, true), size);
// Else, generate an avatar
auto members = convModel->peersForConversation(convId);
if (members.size() < 1)
return avatar;
@ -418,6 +422,16 @@ Utils::conversationAvatar(LRCInstance* instance,
return avatar;
}
QImage
Utils::tempConversationAvatar(const QSize& size)
{
QString img = QByteArrayFromFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ "tmpSwarmImage");
if (img.isEmpty())
return fallbackAvatar(QString(), QString(), size);
return scaleAndFrame(imageFromBase64String(img, true), size);
}
QImage
Utils::imageFromBase64String(const QString& str, bool circleCrop)
{

View file

@ -97,6 +97,7 @@ QImage conversationAvatar(LRCInstance* instance,
QImage getCirclePhoto(const QImage original, int sizePhoto);
QImage halfCrop(const QImage original, bool leftSide);
QColor getAvatarColor(const QString& canonicalUri);
QImage tempConversationAvatar(const QSize& size);
QImage fallbackAvatar(const QString& canonicalUriStr,
const QString& letterStr = {},
const QSize& size = defaultAvatarSize);

View file

@ -31,6 +31,7 @@
#include "api/datatransfermodel.h"
#include <QApplication>
#include <QBuffer>
#include <QClipboard>
#include <QFileInfo>
#include <QRegExp>
@ -135,6 +136,12 @@ UtilsAdapter::getBestName(const QString& accountId, const QString& uid)
return QString();
}
QString
UtilsAdapter::getBestNameForUri(const QString& accountId, const QString& uri)
{
return lrcInstance_->getAccountInfo(accountId).contactModel->bestNameForContact(uri);
}
const QString
UtilsAdapter::getPeerUri(const QString& accountId, const QString& uid)
{
@ -472,6 +479,55 @@ UtilsAdapter::supportedLang()
return result;
}
QString
UtilsAdapter::swarmCreationImage(const QString& imageId) const
{
if (imageId == "temp")
return Utils::QByteArrayFromFile(
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "tmpSwarmImage");
return lrcInstance_->getCurrentConversationModel()->avatar(imageId);
}
void
UtilsAdapter::setSwarmCreationImageFromString(const QString& image, const QString& imageId)
{
// Compress the image before saving
auto img = Utils::imageFromBase64String(image, false);
setSwarmCreationImageFromImage(img);
}
void
UtilsAdapter::setSwarmCreationImageFromFile(const QString& path, const QString& imageId)
{
// Compress the image before saving
auto image = Utils::QByteArrayFromFile(path);
auto img = Utils::imageFromBase64Data(image, false);
setSwarmCreationImageFromImage(img);
}
void
UtilsAdapter::setSwarmCreationImageFromImage(const QImage& image, const QString& imageId)
{
// Compress the image before saving
auto img = Utils::scaleAndFrame(image, QSize(256, 256));
QByteArray ba;
QBuffer bu(&ba);
img.save(&bu, "PNG");
// Save the image
if (imageId == "temp") {
QFile file(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ "tmpSwarmImage");
file.open(QIODevice::WriteOnly);
file.write(ba.toBase64());
file.close();
Q_EMIT lrcInstance_->base64SwarmAvatarChanged();
} else {
lrcInstance_->getCurrentConversationModel()->updateConversationInfo(imageId,
{{"avatar",
ba.toBase64()}});
}
}
bool
UtilsAdapter::getContactPresence(const QString& accountId, const QString& uri)
{

View file

@ -58,6 +58,7 @@ public:
Q_INVOKABLE bool checkStartupLink();
Q_INVOKABLE void setConversationFilter(const QString& filter);
Q_INVOKABLE const QString getBestName(const QString& accountId, const QString& uid);
Q_INVOKABLE QString getBestNameForUri(const QString& accountId, const QString& uri);
Q_INVOKABLE const QString getPeerUri(const QString& accountId, const QString& uid);
Q_INVOKABLE QString getBestId(const QString& accountId);
Q_INVOKABLE const QString getBestId(const QString& accountId, const QString& uid);
@ -91,6 +92,13 @@ public:
Q_INVOKABLE void monitor(const bool& continuous);
Q_INVOKABLE void clearInteractionsCache(const QString& accountId, const QString& convUid);
Q_INVOKABLE QVariantMap supportedLang();
Q_INVOKABLE QString swarmCreationImage(const QString& imageId = "temp") const;
Q_INVOKABLE void setSwarmCreationImageFromString(const QString& image = "",
const QString& imageId = "temp");
Q_INVOKABLE void setSwarmCreationImageFromFile(const QString& path,
const QString& imageId = "temp");
Q_INVOKABLE void setSwarmCreationImageFromImage(const QImage& image,
const QString& imageId = "temp");
// For Swarm details page
Q_INVOKABLE bool getContactPresence(const QString& accountId, const QString& uri);