From: Felix Weilbach Date: Fri, 22 Oct 2021 09:16:38 +0000 (+0200) Subject: Add profile page X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~18^2~117^2~1 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=db337c44570a73e73944fd4bae50f9370122440e;p=nextcloud-desktop.git Add profile page Fix: #3889 Signed-off-by: Felix Weilbach --- diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index c023f2357..3cbca8dbf 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -98,6 +98,7 @@ set(client_SRCS sharelinkwidget.cpp sharemanager.cpp shareusergroupwidget.cpp + profilepagewidget.cpp sharee.cpp sslbutton.cpp sslerrordialog.cpp @@ -115,7 +116,6 @@ set(client_SRCS guiutility.cpp elidedlabel.cpp headerbanner.cpp - iconjob.cpp iconutils.cpp remotewipe.cpp userstatusselectormodel.cpp diff --git a/src/gui/iconjob.cpp b/src/gui/iconjob.cpp deleted file mode 100644 index 2eaed5d0b..000000000 --- a/src/gui/iconjob.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) by Camila Ayres - * - * 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 2 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. - */ - -#include "iconjob.h" - -namespace OCC { - -IconJob::IconJob(const QUrl &url, QObject *parent) : - QObject(parent) -{ - connect(&_accessManager, &QNetworkAccessManager::finished, - this, &IconJob::finished); - - QNetworkRequest request(url); -#if (QT_VERSION >= 0x050600) - request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); -#endif - _accessManager.get(request); -} - -void IconJob::finished(QNetworkReply *reply) -{ - if (reply->error() != QNetworkReply::NoError) - return; - - reply->deleteLater(); - deleteLater(); - emit jobFinished(reply->readAll()); -} -} diff --git a/src/gui/iconjob.h b/src/gui/iconjob.h deleted file mode 100644 index efbbeb3bd..000000000 --- a/src/gui/iconjob.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) by Camila Ayres - * - * 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 2 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. - */ - -#ifndef ICONJOB_H -#define ICONJOB_H - -#include -#include -#include -#include -#include - -namespace OCC { - -/** - * @brief Job to fetch a icon - * @ingroup gui - */ -class IconJob : public QObject -{ - Q_OBJECT -public: - explicit IconJob(const QUrl &url, QObject *parent = nullptr); - -signals: - void jobFinished(QByteArray iconData); - -private slots: - void finished(QNetworkReply *reply); - -private: - QNetworkAccessManager _accessManager; -}; -} - -#endif // ICONJOB_H diff --git a/src/gui/profilepagewidget.cpp b/src/gui/profilepagewidget.cpp new file mode 100644 index 000000000..5bed47d8a --- /dev/null +++ b/src/gui/profilepagewidget.cpp @@ -0,0 +1,46 @@ +#include "profilepagewidget.h" +#include "guiutility.h" +#include "ocsprofileconnector.h" + +namespace OCC { + +ProfilePageMenu::ProfilePageMenu(AccountPtr account, const QString &shareWithUserId, QWidget *parent) + : QWidget(parent) + , _profileConnector(account) +{ + connect(&_profileConnector, &OcsProfileConnector::hovercardFetched, this, &ProfilePageMenu::onHovercardFetched); + connect(&_profileConnector, &OcsProfileConnector::iconLoaded, this, &ProfilePageMenu::onIconLoaded); + _profileConnector.fetchHovercard(shareWithUserId); +} + +ProfilePageMenu::~ProfilePageMenu() = default; + +void ProfilePageMenu::exec(const QPoint &globalPosition) +{ + _menu.exec(globalPosition); +} + +void ProfilePageMenu::onHovercardFetched() +{ + _menu.clear(); + + const auto hovercardActions = _profileConnector.hovercard()._actions; + for (const auto &hovercardAction : hovercardActions) { + const auto action = _menu.addAction(hovercardAction._icon, hovercardAction._title); + const auto link = hovercardAction._link; + connect(action, &QAction::triggered, action, [link](bool) { Utility::openBrowser(link); }); + } +} + +void ProfilePageMenu::onIconLoaded(const std::size_t &hovercardActionIndex) +{ + const auto hovercardActions = _profileConnector.hovercard()._actions; + const auto menuActions = _menu.actions(); + if (hovercardActionIndex >= hovercardActions.size() + || hovercardActionIndex >= static_cast(menuActions.size())) { + return; + } + const auto menuAction = menuActions[static_cast(hovercardActionIndex)]; + menuAction->setIcon(hovercardActions[hovercardActionIndex]._icon); +} +} diff --git a/src/gui/profilepagewidget.h b/src/gui/profilepagewidget.h new file mode 100644 index 000000000..637dbd460 --- /dev/null +++ b/src/gui/profilepagewidget.h @@ -0,0 +1,30 @@ +#pragma once + +#include "ocsprofileconnector.h" + +#include +#include +#include +#include + +#include + +namespace OCC { + +class ProfilePageMenu : public QWidget +{ + Q_OBJECT +public: + explicit ProfilePageMenu(AccountPtr account, const QString &shareWithUserId, QWidget *parent = nullptr); + ~ProfilePageMenu() override; + + void exec(const QPoint &globalPosition); + +private: + void onHovercardFetched(); + void onIconLoaded(const std::size_t &hovercardActionIndex); + + OcsProfileConnector _profileConnector; + QMenu _menu; +}; +} diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp index 0c37c4565..9c9bc3fc0 100644 --- a/src/gui/shareusergroupwidget.cpp +++ b/src/gui/shareusergroupwidget.cpp @@ -12,7 +12,9 @@ * for more details. */ +#include "ocsprofileconnector.h" #include "sharee.h" +#include "tray/usermodel.h" #include "ui_shareusergroupwidget.h" #include "ui_shareuserline.h" #include "shareusergroupwidget.h" @@ -36,7 +38,9 @@ #include #include #include -#include +#include +#include +#include #include #include #include @@ -48,15 +52,37 @@ #include #include #include +#include +#include #include namespace { - const char *passwordIsSetPlaceholder = "●●●●●●●●"; +const char *passwordIsSetPlaceholder = "●●●●●●●●"; + } namespace OCC { +AvatarEventFilter::AvatarEventFilter(QObject *parent) + : QObject(parent) +{ +} + + +bool AvatarEventFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ContextMenu) { + const auto contextMenuEvent = dynamic_cast(event); + if (!contextMenuEvent) { + return false; + } + emit contextMenu(contextMenuEvent->globalPos()); + return true; + } + return QObject::eventFilter(obj, event); +} + ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, const QString &sharePath, const QString &localPath, @@ -465,16 +491,14 @@ void ShareUserGroupWidget::activateShareeLineEdit() _ui->shareeLineEdit->setFocus(); } -ShareUserLine::ShareUserLine(AccountPtr account, - QSharedPointer share, - SharePermissions maxSharingPermissions, - bool isFile, - QWidget *parent) +ShareUserLine::ShareUserLine(AccountPtr account, QSharedPointer share, + SharePermissions maxSharingPermissions, bool isFile, QWidget *parent) : QWidget(parent) , _ui(new Ui::ShareUserLine) , _account(account) , _share(share) , _isFile(isFile) + , _profilePageMenu(account, share->getShareWith()->shareWith()) { Q_ASSERT(_share); _ui->setupUi(this); @@ -618,11 +642,22 @@ ShareUserLine::ShareUserLine(AccountPtr account, _permissionReshare->setVisible(false); } + const auto avatarEventFilter = new AvatarEventFilter(_ui->avatar); + connect(avatarEventFilter, &AvatarEventFilter::contextMenu, this, &ShareUserLine::onAvatarContextMenu); + _ui->avatar->installEventFilter(avatarEventFilter); + loadAvatar(); customizeStyle(); } +void ShareUserLine::onAvatarContextMenu(const QPoint &globalPosition) +{ + if (_share->getShareType() == Share::TypeUser) { + _profilePageMenu.exec(globalPosition); + } +} + void ShareUserLine::loadAvatar() { const int avatarSize = 36; diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h index c63e56d74..599ab521b 100644 --- a/src/gui/shareusergroupwidget.h +++ b/src/gui/shareusergroupwidget.h @@ -19,6 +19,7 @@ #include "sharemanager.h" #include "sharepermissions.h" #include "sharee.h" +#include "profilepagewidget.h" #include "QProgressIndicator.h" #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include class QAction; @@ -44,6 +46,21 @@ class SyncResult; class Share; class ShareManager; +class AvatarEventFilter : public QObject +{ + Q_OBJECT + +public: + explicit AvatarEventFilter(QObject *parent = nullptr); + +signals: + void clicked(); + void contextMenu(const QPoint &globalPosition); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + /** * @brief The ShareDialog (user/group) class * @ingroup gui @@ -166,6 +183,8 @@ private slots: void slotConfirmPasswordClicked(); + void onAvatarContextMenu(const QPoint &globalPosition); + private: void displayPermissions(); void loadAvatar(); @@ -197,6 +216,8 @@ private: QSharedPointer _share; bool _isFile; + ProfilePageMenu _profilePageMenu; + // _permissionEdit is a checkbox QAction *_permissionReshare; QAction *_deleteShareButton; diff --git a/src/gui/shareuserline.ui b/src/gui/shareuserline.ui index e79614a06..5067a3120 100644 --- a/src/gui/shareuserline.ui +++ b/src/gui/shareuserline.ui @@ -6,8 +6,8 @@ 0 0 - 980 - 239 + 899 + 310 @@ -301,33 +301,519 @@ + + + 255 + 255 + 255 + + + + + + + 65 + 70 + 84 + + + + + + + 95 + 103 + 127 + + + + + + + 49 + 49 + 49 + + + + + + + 0 + 0 + 0 + + + + + 25 + 25 + 25 + + + + + + + 255 + 255 + 255 + + + + + + 255 + 255 + 255 + + + + + + + 64 + 69 + 82 + + + + + + + 56 + 60 + 74 + + + + + + + 0 0 0 + + + + 82 + 148 + 226 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 157 + 255 + + + + + + + 158 + 79 + 255 + + + + + + + 60 + 67 + 79 + + + + + + + 0 + 0 + 0 + + + + + + + 238 + 252 + 255 + + + + + + + 255 + 255 + 255 + + + + + + 255 + 255 + 255 + + + + + 65 + 70 + 84 + + + + + + + 95 + 103 + 127 + + + + + + + 49 + 49 + 49 + + + + + + + 0 + 0 + 0 + + + + + + + 25 + 25 + 25 + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 64 + 69 + 82 + + + + + + + 56 + 60 + 74 + + + + + + + 0 + 0 + 0 + + + + + + + 82 + 149 + 225 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 157 + 255 + + + + + + + 158 + 79 + 255 + + + + + + + 60 + 67 + 79 + + + + + + + 0 0 0 + + + + 238 + 252 + 255 + + + + + + + 255 + 255 + 255 + + + + + + 255 + 255 + 255 + + + + + + + 65 + 70 + 84 + + + + + + + 95 + 103 + 127 + + + + + + + 49 + 49 + 49 + + + + + + + 0 + 0 + 0 + + + + + + + 25 + 25 + 25 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 255 + + + + + + + 64 + 69 + 82 + + + + + + + 56 + 60 + 74 + + + + + + + 0 + 0 + 0 + + + + - 123 - 121 - 134 + 82 + 148 + 226 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 157 + 255 + + + + + + + 158 + 79 + 255 + + + + + + + 60 + 67 + 79 + + + + + + + 0 + 0 + 0 + + + + + + + 238 + 252 + 255 + + + + + + + 255 + 255 + 255 diff --git a/src/libsync/CMakeLists.txt b/src/libsync/CMakeLists.txt index 0ec0ccc0e..d58e27d21 100644 --- a/src/libsync/CMakeLists.txt +++ b/src/libsync/CMakeLists.txt @@ -29,6 +29,7 @@ set(libsync_SRCS configfile.cpp abstractnetworkjob.cpp networkjobs.cpp + iconjob.cpp owncloudpropagator.cpp nextcloudtheme.cpp abstractpropagateremotedeleteencrypted.cpp @@ -58,6 +59,7 @@ set(libsync_SRCS datetimeprovider.cpp ocsuserstatusconnector.cpp userstatusconnector.cpp + ocsprofileconnector.cpp creds/dummycredentials.cpp creds/abstractcredentials.cpp creds/credentialscommon.cpp diff --git a/src/libsync/iconjob.cpp b/src/libsync/iconjob.cpp new file mode 100644 index 000000000..0c9017125 --- /dev/null +++ b/src/libsync/iconjob.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) by Camila Ayres + * + * 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 2 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. + */ + +#include "iconjob.h" + +namespace OCC { + +IconJob::IconJob(const QUrl &url, QObject *parent) : + QObject(parent) +{ + connect(&_accessManager, &QNetworkAccessManager::finished, + this, &IconJob::finished); + + QNetworkRequest request(url); +#if (QT_VERSION >= 0x050600) + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); +#endif + _accessManager.get(request); +} + +void IconJob::finished(QNetworkReply *reply) +{ + reply->deleteLater(); + deleteLater(); + + const auto networkError = reply->error(); + if (networkError != QNetworkReply::NoError) { + emit error(networkError); + return; + } + + emit jobFinished(reply->readAll()); +} +} diff --git a/src/libsync/iconjob.h b/src/libsync/iconjob.h new file mode 100644 index 000000000..719ea3be4 --- /dev/null +++ b/src/libsync/iconjob.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) by Camila Ayres + * + * 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 2 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. + */ + +#ifndef ICONJOB_H +#define ICONJOB_H + +#include "owncloudlib.h" + +#include +#include +#include +#include +#include + +namespace OCC { + +/** + * @brief Job to fetch a icon + * @ingroup gui + */ +class OWNCLOUDSYNC_EXPORT IconJob : public QObject +{ + Q_OBJECT +public: + explicit IconJob(const QUrl &url, QObject *parent = nullptr); + +signals: + void jobFinished(QByteArray iconData); + void error(QNetworkReply::NetworkError errorType); + +private slots: + void finished(QNetworkReply *reply); + +private: + QNetworkAccessManager _accessManager; +}; +} + +#endif // ICONJOB_H diff --git a/src/libsync/ocsprofileconnector.cpp b/src/libsync/ocsprofileconnector.cpp new file mode 100644 index 000000000..5bda93cc4 --- /dev/null +++ b/src/libsync/ocsprofileconnector.cpp @@ -0,0 +1,168 @@ +#include "ocsprofileconnector.h" +#include "accountfwd.h" +#include "common/result.h" +#include "networkjobs.h" +#include "iconjob.h" +#include "theme.h" +#include "account.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +Q_LOGGING_CATEGORY(lcOcsProfileConnector, "nextcloud.gui.ocsprofileconnector", QtInfoMsg) + +OCC::HovercardAction jsonToAction(const QJsonObject &jsonActionObject) +{ + const auto iconUrl = jsonActionObject.value(QStringLiteral("icon")).toString(QStringLiteral("no-icon")); + QPixmap iconPixmap; + OCC::HovercardAction hovercardAction{ + jsonActionObject.value(QStringLiteral("title")).toString(QStringLiteral("No title")), iconUrl, + jsonActionObject.value(QStringLiteral("hyperlink")).toString(QStringLiteral("no-link"))}; + if (QPixmapCache::find(iconUrl, &iconPixmap)) { + hovercardAction._icon = iconPixmap; + } + return hovercardAction; +} + +OCC::Hovercard jsonToHovercard(const QJsonArray &jsonDataArray) +{ + OCC::Hovercard hovercard; + hovercard._actions.reserve(jsonDataArray.size()); + for (const auto &jsonEntry : jsonDataArray) { + Q_ASSERT(jsonEntry.isObject()); + if (!jsonEntry.isObject()) { + continue; + } + hovercard._actions.push_back(jsonToAction(jsonEntry.toObject())); + } + return hovercard; +} + +OCC::Optional createPixmapFromSvgData(const QByteArray &iconData) +{ + QSvgRenderer svgRenderer; + if (!svgRenderer.load(iconData)) { + return {}; + } + QSize imageSize{16, 16}; + if (OCC::Theme::isHidpi()) { + imageSize = QSize{32, 32}; + } + QImage scaledSvg(imageSize, QImage::Format_ARGB32); + scaledSvg.fill("transparent"); + QPainter svgPainter{&scaledSvg}; + svgRenderer.render(&svgPainter); + return QPixmap::fromImage(scaledSvg); +} + +OCC::Optional iconDataToPixmap(const QByteArray iconData) +{ + if (!iconData.startsWith("serverVersionInt() < Account::makeServerVersion(23, 0, 0)) { + qInfo(lcOcsProfileConnector) << "Server version" << _account->serverVersion() + << "does not support profile page"; + emit error(); + return; + } + const QString url = QStringLiteral("/ocs/v2.php/hovercard/v1/%1").arg(userId); + const auto job = new JsonApiJob(_account, url, this); + connect(job, &JsonApiJob::jsonReceived, this, &OcsProfileConnector::onHovercardFetched); + job->start(); +} + +void OcsProfileConnector::onHovercardFetched(const QJsonDocument &json, int statusCode) +{ + qCDebug(lcOcsProfileConnector) << "Hovercard fetched:" << json; + + if (statusCode != 200) { + qCInfo(lcOcsProfileConnector) << "Fetching of hovercard finished with status code" << statusCode; + return; + } + const auto jsonData = json.object().value("ocs").toObject().value("data").toObject().value("actions"); + Q_ASSERT(jsonData.isArray()); + _currentHovercard = jsonToHovercard(jsonData.toArray()); + fetchIcons(); + emit hovercardFetched(); +} + +void OcsProfileConnector::setHovercardActionIcon(const std::size_t index, const QPixmap &pixmap) +{ + auto &hovercardAction = _currentHovercard._actions[index]; + QPixmapCache::insert(hovercardAction._iconUrl.toString(), pixmap); + hovercardAction._icon = pixmap; + emit iconLoaded(index); +} + +void OcsProfileConnector::loadHovercardActionIcon(const std::size_t hovercardActionIndex, const QByteArray &iconData) +{ + if (hovercardActionIndex >= _currentHovercard._actions.size()) { + // Note: Probably could do more checking, like checking if the url is still the same. + return; + } + const auto icon = iconDataToPixmap(iconData); + if (icon.isValid()) { + setHovercardActionIcon(hovercardActionIndex, icon.get()); + return; + } + qCWarning(lcOcsProfileConnector) << "Could not load Svg icon from data" << iconData; +} + +void OcsProfileConnector::startFetchIconJob(const std::size_t hovercardActionIndex) +{ + const auto hovercardAction = _currentHovercard._actions[hovercardActionIndex]; + const auto iconJob = new IconJob{hovercardAction._iconUrl, this}; + connect(iconJob, &IconJob::jobFinished, + [this, hovercardActionIndex](QByteArray iconData) { loadHovercardActionIcon(hovercardActionIndex, iconData); }); + connect(iconJob, &IconJob::error, this, [](QNetworkReply::NetworkError errorType) { + qCWarning(lcOcsProfileConnector) << "Could not fetch icon:" << errorType; + }); +} + +void OcsProfileConnector::fetchIcons() +{ + for (auto hovercardActionIndex = 0u; hovercardActionIndex < _currentHovercard._actions.size(); + ++hovercardActionIndex) { + startFetchIconJob(hovercardActionIndex); + } +} + +const Hovercard &OcsProfileConnector::hovercard() const +{ + return _currentHovercard; +} +} diff --git a/src/libsync/ocsprofileconnector.h b/src/libsync/ocsprofileconnector.h new file mode 100644 index 000000000..69cd60116 --- /dev/null +++ b/src/libsync/ocsprofileconnector.h @@ -0,0 +1,58 @@ +#pragma once + +#include "accountfwd.h" +#include "owncloudlib.h" + +#include +#include +#include +#include + +namespace OCC { + +struct OWNCLOUDSYNC_EXPORT HovercardAction +{ +public: + HovercardAction(); + HovercardAction(QString title, QUrl iconUrl, QUrl link); + + QString _title; + QUrl _iconUrl; + QPixmap _icon; + QUrl _link; +}; + +struct OWNCLOUDSYNC_EXPORT Hovercard +{ + std::vector _actions; +}; + +class OWNCLOUDSYNC_EXPORT OcsProfileConnector : public QObject +{ + Q_OBJECT +public: + explicit OcsProfileConnector(AccountPtr account, QObject *parent = nullptr); + + void fetchHovercard(const QString &userId); + const Hovercard &hovercard() const; + +signals: + void error(); + void hovercardFetched(); + void iconLoaded(const std::size_t hovercardActionIndex); + +private: + void onHovercardFetched(const QJsonDocument &json, int statusCode); + + void fetchIcons(); + void startFetchIconJob(const std::size_t hovercardActionIndex); + void setHovercardActionIcon(const std::size_t index, const QPixmap &pixmap); + void loadHovercardActionIcon(const std::size_t hovercardActionIndex, const QByteArray &iconData); + + AccountPtr _account; + Hovercard _currentHovercard; +}; +} + +Q_DECLARE_METATYPE(OCC::HovercardAction) +Q_DECLARE_METATYPE(OCC::Hovercard)