From a5cfffef6dc3665c1ce5afea85f217c47d27d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= <sebastien.blin@savoirfairelinux.com> Date: Fri, 11 Feb 2022 16:25:42 -0500 Subject: [PATCH] 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 --- src/avatarimageprovider.h | 7 +- src/avatarregistry.cpp | 10 +++ src/commoncomponents/PhotoboothView.qml | 33 +++++-- src/constant/JamiStrings.qml | 1 + src/lrcinstance.h | 1 + src/mainview/MainView.qml | 8 ++ src/mainview/components/NewSwarmPage.qml | 87 ++++++++++++++++++- src/mainview/components/SidePanel.qml | 72 ++++++++++++--- src/mainview/components/SwarmDetailsPanel.qml | 10 +-- src/utils.cpp | 14 +++ src/utils.h | 1 + src/utilsadapter.cpp | 56 ++++++++++++ src/utilsadapter.h | 8 ++ 13 files changed, 278 insertions(+), 30 deletions(-) diff --git a/src/avatarimageprovider.h b/src/avatarimageprovider.h index 285429a1c..6e5a7cb38 100644 --- a/src/avatarimageprovider.h +++ b/src/avatarimageprovider.h @@ -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); diff --git a/src/avatarregistry.cpp b/src/avatarregistry.cpp index 8bd97b3fa..aa0072c72 100644 --- a/src/avatarregistry.cpp +++ b/src/avatarregistry.cpp @@ -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 diff --git a/src/commoncomponents/PhotoboothView.qml b/src/commoncomponents/PhotoboothView.qml index 828ab91fa..c5c8d2f24 100644 --- a/src/commoncomponents/PhotoboothView.qml +++ b/src/commoncomponents/PhotoboothView.qml @@ -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) + } } } diff --git a/src/constant/JamiStrings.qml b/src/constant/JamiStrings.qml index 32a2589ef..cb80596a3 100644 --- a/src/constant/JamiStrings.qml +++ b/src/constant/JamiStrings.qml @@ -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") } diff --git a/src/lrcinstance.h b/src/lrcinstance.h index 0b4e40269..900f4207f 100644 --- a/src/lrcinstance.h +++ b/src/lrcinstance.h @@ -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_; diff --git a/src/mainview/MainView.qml b/src/mainview/MainView.qml index 0f8008ab9..294d658a9 100644 --- a/src/mainview/MainView.qml +++ b/src/mainview/MainView.qml @@ -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() diff --git a/src/mainview/components/NewSwarmPage.qml b/src/mainview/components/NewSwarmPage.qml index 79dfdd5e1..58b576bbf 100644 --- a/src/mainview/components/NewSwarmPage.qml +++ b/src/mainview/components/NewSwarmPage.qml @@ -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()) } } } diff --git a/src/mainview/components/SidePanel.qml b/src/mainview/components/SidePanel.qml index db4160977..25bf1b11d 100644 --- a/src/mainview/components/SidePanel.qml +++ b/src/mainview/components/SidePanel.qml @@ -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) + } + + // We can't have more than 8 participants yet. + if (newHm.length > 8) { + return false } - highlightedMembers = result + + 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 diff --git a/src/mainview/components/SwarmDetailsPanel.qml b/src/mainview/components/SwarmDetailsPanel.qml index f5adf0f10..12e342ccd 100644 --- a/src/mainview/components/SwarmDetailsPanel.qml +++ b/src/mainview/components/SwarmDetailsPanel.qml @@ -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 { diff --git a/src/utils.cpp b/src/utils.cpp index 65afcd260..3720d54ac 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -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) { diff --git a/src/utils.h b/src/utils.h index cf6fd8ee0..174dfff7e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -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); diff --git a/src/utilsadapter.cpp b/src/utilsadapter.cpp index 300dc6297..f224f4bdc 100644 --- a/src/utilsadapter.cpp +++ b/src/utilsadapter.cpp @@ -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) { diff --git a/src/utilsadapter.h b/src/utilsadapter.h index cf1164fcf..7748a07e1 100644 --- a/src/utilsadapter.h +++ b/src/utilsadapter.h @@ -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); -- GitLab