1
0
Fork 0
mirror of https://git.jami.net/savoirfairelinux/jami-client-qt.git synced 2025-08-17 13:15:46 +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); auto type = idInfo.at(0);
if (type == "conversation") if (type == "conversation") {
if (imageId == "temp")
return Utils::tempConversationAvatar(requestedSize);
return Utils::conversationAvatar(lrcInstance_, imageId, requestedSize); return Utils::conversationAvatar(lrcInstance_, imageId, requestedSize);
else if (type == "account") } else if (type == "account")
return Utils::accountPhoto(lrcInstance_, imageId, requestedSize); return Utils::accountPhoto(lrcInstance_, imageId, requestedSize);
else if (type == "contact") else if (type == "contact")
return Utils::contactPhoto(lrcInstance_, imageId, requestedSize); return Utils::contactPhoto(lrcInstance_, imageId, requestedSize);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,12 +33,95 @@ Rectangle {
color: JamiTheme.chatviewBgColor color: JamiTheme.chatviewBgColor
signal createSwarmClicked(string title, string description, string avatar) 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 { ColumnLayout {
id: mainLayout id: mainLayout
anchors.centerIn: root anchors.centerIn: root
PhotoboothView {
id: currentAccountAvatar
Layout.alignment: Qt.AlignCenter
newConversation: true
imageId: root.visible ? "temp" : ""
avatarSize: 180
}
EditableLineEdit { EditableLineEdit {
id: title id: title
Layout.alignment: Qt.AlignCenter Layout.alignment: Qt.AlignCenter
@ -83,7 +166,7 @@ Rectangle {
text: JamiStrings.createTheSwarm text: JamiStrings.createTheSwarm
onClicked: { 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 highlighted: []
property var highlightedMembers: [] property var highlightedMembers: []
function refreshHighlighted() { function refreshHighlighted(convId, highlightedStatus) {
var result = [] var newH = root.highlighted
for (var idx in highlighted) { var newHm = root.highlightedMembers
var convId = highlighted[idx]
if (highlightedStatus) {
var item = ConversationsAdapter.getConvInfoMap(convId) var item = ConversationsAdapter.getConvInfoMap(convId)
var added = false
for (var idx in item.uris) { for (var idx in item.uris) {
var uri = item.uris[idx] var uri = item.uris[idx]
if (!result.indexOf(uri) != -1 && uri != CurrentAccount.uri) { if (!Array.from(newHm).find(r => r.uri === uri) && uri != CurrentAccount.uri) {
result.push(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) 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) { function showSwarmListView(v) {
@ -280,23 +326,21 @@ Rectangle {
onVisibleChanged: { onVisibleChanged: {
if (!visible) { if (!visible) {
highlighted = false highlighted = false
root.refreshHighlighted() root.clearHighlighted()
} }
} }
onHighlightedChanged: function onHighlightedChanged() { onHighlightedChanged: function onHighlightedChanged() {
var currentHighlighted = root.highlighted var currentHighlighted = root.highlighted
if (!root.refreshHighlighted(convId, highlighted)) {
highlighted = false
return
}
if (highlighted) { if (highlighted) {
root.highlighted.push(convId) root.highlighted.push(convId)
} else { } else {
root.highlighted = Array.from(root.highlighted).filter(r => r !== convId) 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 currentIndex: model.currentFilteredRow

View file

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

View file

@ -395,6 +395,10 @@ Utils::conversationAvatar(LRCInstance* instance,
auto& accInfo = instance->accountModel().getAccountInfo( auto& accInfo = instance->accountModel().getAccountInfo(
accountId.isEmpty() ? instance->get_currentAccountId() : accountId); accountId.isEmpty() ? instance->get_currentAccountId() : accountId);
auto* convModel = accInfo.conversationModel.get(); 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); auto members = convModel->peersForConversation(convId);
if (members.size() < 1) if (members.size() < 1)
return avatar; return avatar;
@ -418,6 +422,16 @@ Utils::conversationAvatar(LRCInstance* instance,
return avatar; 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 QImage
Utils::imageFromBase64String(const QString& str, bool circleCrop) 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 getCirclePhoto(const QImage original, int sizePhoto);
QImage halfCrop(const QImage original, bool leftSide); QImage halfCrop(const QImage original, bool leftSide);
QColor getAvatarColor(const QString& canonicalUri); QColor getAvatarColor(const QString& canonicalUri);
QImage tempConversationAvatar(const QSize& size);
QImage fallbackAvatar(const QString& canonicalUriStr, QImage fallbackAvatar(const QString& canonicalUriStr,
const QString& letterStr = {}, const QString& letterStr = {},
const QSize& size = defaultAvatarSize); const QSize& size = defaultAvatarSize);

View file

@ -31,6 +31,7 @@
#include "api/datatransfermodel.h" #include "api/datatransfermodel.h"
#include <QApplication> #include <QApplication>
#include <QBuffer>
#include <QClipboard> #include <QClipboard>
#include <QFileInfo> #include <QFileInfo>
#include <QRegExp> #include <QRegExp>
@ -135,6 +136,12 @@ UtilsAdapter::getBestName(const QString& accountId, const QString& uid)
return QString(); return QString();
} }
QString
UtilsAdapter::getBestNameForUri(const QString& accountId, const QString& uri)
{
return lrcInstance_->getAccountInfo(accountId).contactModel->bestNameForContact(uri);
}
const QString const QString
UtilsAdapter::getPeerUri(const QString& accountId, const QString& uid) UtilsAdapter::getPeerUri(const QString& accountId, const QString& uid)
{ {
@ -472,6 +479,55 @@ UtilsAdapter::supportedLang()
return result; 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 bool
UtilsAdapter::getContactPresence(const QString& accountId, const QString& uri) UtilsAdapter::getContactPresence(const QString& accountId, const QString& uri)
{ {

View file

@ -58,6 +58,7 @@ public:
Q_INVOKABLE bool checkStartupLink(); Q_INVOKABLE bool checkStartupLink();
Q_INVOKABLE void setConversationFilter(const QString& filter); Q_INVOKABLE void setConversationFilter(const QString& filter);
Q_INVOKABLE const QString getBestName(const QString& accountId, const QString& uid); 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 const QString getPeerUri(const QString& accountId, const QString& uid);
Q_INVOKABLE QString getBestId(const QString& accountId); Q_INVOKABLE QString getBestId(const QString& accountId);
Q_INVOKABLE const QString getBestId(const QString& accountId, const QString& uid); 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 monitor(const bool& continuous);
Q_INVOKABLE void clearInteractionsCache(const QString& accountId, const QString& convUid); Q_INVOKABLE void clearInteractionsCache(const QString& accountId, const QString& convUid);
Q_INVOKABLE QVariantMap supportedLang(); 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 // For Swarm details page
Q_INVOKABLE bool getContactPresence(const QString& accountId, const QString& uri); Q_INVOKABLE bool getContactPresence(const QString& accountId, const QString& uri);