From a5cfffef6dc3665c1ce5afea85f217c47d27d314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= 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 285429a1..6e5a7cb3 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 8bd97b3f..aa0072c7 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 828ab91f..c5c8d2f2 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 32a2589e..cb80596a 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 0b4e4026..900f4207 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_; diff --git a/src/mainview/MainView.qml b/src/mainview/MainView.qml index 0f8008ab..294d658a 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 79dfdd5e..58b576bb 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 db416097..25bf1b11 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) } - 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 diff --git a/src/mainview/components/SwarmDetailsPanel.qml b/src/mainview/components/SwarmDetailsPanel.qml index f5adf0f1..12e342cc 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 65afcd26..3720d54a 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 cf6fd8ee..174dfff7 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 300dc629..f224f4bd 100644 --- a/src/utilsadapter.cpp +++ b/src/utilsadapter.cpp @@ -31,6 +31,7 @@ #include "api/datatransfermodel.h" #include +#include #include #include #include @@ -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 cf1164fc..7748a07e 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);