Merge pull request #3935 from nextcloud/feature/profile-page

Add profile page
This commit is contained in:
Felix Weilbach 2021-11-10 14:02:23 +01:00 committed by GitHub
commit 48a8085453
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 885 additions and 32 deletions

View file

@ -98,6 +98,7 @@ set(client_SRCS
sharelinkwidget.cpp sharelinkwidget.cpp
sharemanager.cpp sharemanager.cpp
shareusergroupwidget.cpp shareusergroupwidget.cpp
profilepagewidget.cpp
sharee.cpp sharee.cpp
sslbutton.cpp sslbutton.cpp
sslerrordialog.cpp sslerrordialog.cpp
@ -115,7 +116,6 @@ set(client_SRCS
guiutility.cpp guiutility.cpp
elidedlabel.cpp elidedlabel.cpp
headerbanner.cpp headerbanner.cpp
iconjob.cpp
iconutils.cpp iconutils.cpp
remotewipe.cpp remotewipe.cpp
userstatusselectormodel.cpp userstatusselectormodel.cpp

View file

@ -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<std::size_t>(menuActions.size())) {
return;
}
const auto menuAction = menuActions[static_cast<int>(hovercardActionIndex)];
menuAction->setIcon(hovercardActions[hovercardActionIndex]._icon);
}
}

View file

@ -0,0 +1,30 @@
#pragma once
#include "ocsprofileconnector.h"
#include <QBoxLayout>
#include <QLabel>
#include <account.h>
#include <QMenu>
#include <cstddef>
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;
};
}

View file

@ -12,7 +12,9 @@
* for more details. * for more details.
*/ */
#include "ocsprofileconnector.h"
#include "sharee.h" #include "sharee.h"
#include "tray/usermodel.h"
#include "ui_shareusergroupwidget.h" #include "ui_shareusergroupwidget.h"
#include "ui_shareuserline.h" #include "ui_shareuserline.h"
#include "shareusergroupwidget.h" #include "shareusergroupwidget.h"
@ -36,7 +38,9 @@
#include <QFileInfo> #include <QFileInfo>
#include <QAbstractProxyModel> #include <QAbstractProxyModel>
#include <QCompleter> #include <QCompleter>
#include <qlayout.h> #include <QBoxLayout>
#include <QIcon>
#include <QLayout>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QMenu> #include <QMenu>
#include <QAction> #include <QAction>
@ -48,15 +52,37 @@
#include <QPainter> #include <QPainter>
#include <QListWidget> #include <QListWidget>
#include <QSvgRenderer> #include <QSvgRenderer>
#include <QPushButton>
#include <QContextMenuEvent>
#include <cstring> #include <cstring>
namespace { namespace {
const char *passwordIsSetPlaceholder = "●●●●●●●●"; const char *passwordIsSetPlaceholder = "●●●●●●●●";
} }
namespace OCC { 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<QContextMenuEvent *>(event);
if (!contextMenuEvent) {
return false;
}
emit contextMenu(contextMenuEvent->globalPos());
return true;
}
return QObject::eventFilter(obj, event);
}
ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account, ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
const QString &sharePath, const QString &sharePath,
const QString &localPath, const QString &localPath,
@ -465,16 +491,14 @@ void ShareUserGroupWidget::activateShareeLineEdit()
_ui->shareeLineEdit->setFocus(); _ui->shareeLineEdit->setFocus();
} }
ShareUserLine::ShareUserLine(AccountPtr account, ShareUserLine::ShareUserLine(AccountPtr account, QSharedPointer<UserGroupShare> share,
QSharedPointer<UserGroupShare> share, SharePermissions maxSharingPermissions, bool isFile, QWidget *parent)
SharePermissions maxSharingPermissions,
bool isFile,
QWidget *parent)
: QWidget(parent) : QWidget(parent)
, _ui(new Ui::ShareUserLine) , _ui(new Ui::ShareUserLine)
, _account(account) , _account(account)
, _share(share) , _share(share)
, _isFile(isFile) , _isFile(isFile)
, _profilePageMenu(account, share->getShareWith()->shareWith())
{ {
Q_ASSERT(_share); Q_ASSERT(_share);
_ui->setupUi(this); _ui->setupUi(this);
@ -618,11 +642,22 @@ ShareUserLine::ShareUserLine(AccountPtr account,
_permissionReshare->setVisible(false); _permissionReshare->setVisible(false);
} }
const auto avatarEventFilter = new AvatarEventFilter(_ui->avatar);
connect(avatarEventFilter, &AvatarEventFilter::contextMenu, this, &ShareUserLine::onAvatarContextMenu);
_ui->avatar->installEventFilter(avatarEventFilter);
loadAvatar(); loadAvatar();
customizeStyle(); customizeStyle();
} }
void ShareUserLine::onAvatarContextMenu(const QPoint &globalPosition)
{
if (_share->getShareType() == Share::TypeUser) {
_profilePageMenu.exec(globalPosition);
}
}
void ShareUserLine::loadAvatar() void ShareUserLine::loadAvatar()
{ {
const int avatarSize = 36; const int avatarSize = 36;

View file

@ -19,6 +19,7 @@
#include "sharemanager.h" #include "sharemanager.h"
#include "sharepermissions.h" #include "sharepermissions.h"
#include "sharee.h" #include "sharee.h"
#include "profilepagewidget.h"
#include "QProgressIndicator.h" #include "QProgressIndicator.h"
#include <QDialog> #include <QDialog>
#include <QWidget> #include <QWidget>
@ -26,6 +27,7 @@
#include <QList> #include <QList>
#include <QVector> #include <QVector>
#include <QTimer> #include <QTimer>
#include <qpushbutton.h>
#include <qscrollarea.h> #include <qscrollarea.h>
class QAction; class QAction;
@ -44,6 +46,21 @@ class SyncResult;
class Share; class Share;
class ShareManager; 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 * @brief The ShareDialog (user/group) class
* @ingroup gui * @ingroup gui
@ -166,6 +183,8 @@ private slots:
void slotConfirmPasswordClicked(); void slotConfirmPasswordClicked();
void onAvatarContextMenu(const QPoint &globalPosition);
private: private:
void displayPermissions(); void displayPermissions();
void loadAvatar(); void loadAvatar();
@ -197,6 +216,8 @@ private:
QSharedPointer<UserGroupShare> _share; QSharedPointer<UserGroupShare> _share;
bool _isFile; bool _isFile;
ProfilePageMenu _profilePageMenu;
// _permissionEdit is a checkbox // _permissionEdit is a checkbox
QAction *_permissionReshare; QAction *_permissionReshare;
QAction *_deleteShareButton; QAction *_deleteShareButton;

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>980</width> <width>899</width>
<height>239</height> <height>310</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -302,32 +302,518 @@
<active> <active>
<colorrole role="WindowText"> <colorrole role="WindowText">
<brush brushstyle="SolidPattern"> <brush brushstyle="SolidPattern">
<color alpha="255"> <color alpha="200">
<red>255</red> <red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>65</red>
<green>70</green>
<blue>84</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>95</red>
<green>103</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>49</red>
<green>49</green>
<blue>49</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green> <green>0</green>
<blue>0</blue> <blue>0</blue>
</color> </color>
</brush> </brush>
</colorrole> </colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>25</red>
<green>25</green>
<blue>25</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="200">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="200">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>64</red>
<green>69</green>
<blue>82</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>56</red>
<green>60</green>
<blue>74</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>82</red>
<green>148</green>
<blue>226</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Link">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>157</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="LinkVisited">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>158</red>
<green>79</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>60</red>
<green>67</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>238</red>
<green>252</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</active> </active>
<inactive> <inactive>
<colorrole role="WindowText"> <colorrole role="WindowText">
<brush brushstyle="SolidPattern"> <brush brushstyle="SolidPattern">
<color alpha="255"> <color alpha="200">
<red>255</red> <red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>65</red>
<green>70</green>
<blue>84</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>95</red>
<green>103</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>49</red>
<green>49</green>
<blue>49</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green> <green>0</green>
<blue>0</blue> <blue>0</blue>
</color> </color>
</brush> </brush>
</colorrole> </colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>25</red>
<green>25</green>
<blue>25</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="200">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="200">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>64</red>
<green>69</green>
<blue>82</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>56</red>
<green>60</green>
<blue>74</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>82</red>
<green>149</green>
<blue>225</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Link">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>157</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="LinkVisited">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>158</red>
<green>79</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>60</red>
<green>67</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>238</red>
<green>252</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
</inactive> </inactive>
<disabled> <disabled>
<colorrole role="WindowText"> <colorrole role="WindowText">
<brush brushstyle="SolidPattern">
<color alpha="115">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Button">
<brush brushstyle="SolidPattern"> <brush brushstyle="SolidPattern">
<color alpha="255"> <color alpha="255">
<red>123</red> <red>65</red>
<green>121</green> <green>70</green>
<blue>134</blue> <blue>84</blue>
</color>
</brush>
</colorrole>
<colorrole role="Light">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>95</red>
<green>103</green>
<blue>127</blue>
</color>
</brush>
</colorrole>
<colorrole role="Midlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>49</red>
<green>49</green>
<blue>49</blue>
</color>
</brush>
</colorrole>
<colorrole role="Dark">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Mid">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>25</red>
<green>25</green>
<blue>25</blue>
</color>
</brush>
</colorrole>
<colorrole role="Text">
<brush brushstyle="SolidPattern">
<color alpha="115">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="ButtonText">
<brush brushstyle="SolidPattern">
<color alpha="115">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Base">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>64</red>
<green>69</green>
<blue>82</blue>
</color>
</brush>
</colorrole>
<colorrole role="Window">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>56</red>
<green>60</green>
<blue>74</blue>
</color>
</brush>
</colorrole>
<colorrole role="Shadow">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="Highlight">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>82</red>
<green>148</green>
<blue>226</blue>
</color>
</brush>
</colorrole>
<colorrole role="HighlightedText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="Link">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>157</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="LinkVisited">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>158</red>
<green>79</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="AlternateBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>60</red>
<green>67</green>
<blue>79</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipBase">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>0</red>
<green>0</green>
<blue>0</blue>
</color>
</brush>
</colorrole>
<colorrole role="ToolTipText">
<brush brushstyle="SolidPattern">
<color alpha="255">
<red>238</red>
<green>252</green>
<blue>255</blue>
</color>
</brush>
</colorrole>
<colorrole role="PlaceholderText">
<brush brushstyle="SolidPattern">
<color alpha="128">
<red>255</red>
<green>255</green>
<blue>255</blue>
</color> </color>
</brush> </brush>
</colorrole> </colorrole>

View file

@ -113,7 +113,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._icon = json.value("icon").toString(); a._icon = json.value("icon").toString();
if (!a._icon.isEmpty()) { if (!a._icon.isEmpty()) {
auto *iconJob = new IconJob(QUrl(a._icon)); auto *iconJob = new IconJob(_accountState->account(), QUrl(a._icon));
iconJob->setProperty("activityId", a._id); iconJob->setProperty("activityId", a._id);
connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded); connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded);
} }

View file

@ -29,6 +29,7 @@ set(libsync_SRCS
configfile.cpp configfile.cpp
abstractnetworkjob.cpp abstractnetworkjob.cpp
networkjobs.cpp networkjobs.cpp
iconjob.cpp
owncloudpropagator.cpp owncloudpropagator.cpp
nextcloudtheme.cpp nextcloudtheme.cpp
abstractpropagateremotedeleteencrypted.cpp abstractpropagateremotedeleteencrypted.cpp
@ -58,6 +59,7 @@ set(libsync_SRCS
datetimeprovider.cpp datetimeprovider.cpp
ocsuserstatusconnector.cpp ocsuserstatusconnector.cpp
userstatusconnector.cpp userstatusconnector.cpp
ocsprofileconnector.cpp
creds/dummycredentials.cpp creds/dummycredentials.cpp
creds/abstractcredentials.cpp creds/abstractcredentials.cpp
creds/credentialscommon.cpp creds/credentialscommon.cpp

View file

@ -16,26 +16,31 @@
namespace OCC { namespace OCC {
IconJob::IconJob(const QUrl &url, QObject *parent) : IconJob::IconJob(AccountPtr account, const QUrl &url, QObject *parent)
QObject(parent) : QObject(parent)
{ {
connect(&_accessManager, &QNetworkAccessManager::finished,
this, &IconJob::finished);
QNetworkRequest request(url); QNetworkRequest request(url);
#if (QT_VERSION >= 0x050600) #if (QT_VERSION >= 0x050600)
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
#endif #endif
_accessManager.get(request); const auto reply = account->sendRawRequest(QByteArrayLiteral("GET"), url, request);
connect(reply, &QNetworkReply::finished, this, &IconJob::finished);
} }
void IconJob::finished(QNetworkReply *reply) void IconJob::finished()
{ {
if (reply->error() != QNetworkReply::NoError) const auto reply = qobject_cast<QNetworkReply *>(sender());
if (!reply) {
return; return;
}
reply->deleteLater();
deleteLater(); deleteLater();
const auto networkError = reply->error();
if (networkError != QNetworkReply::NoError) {
emit error(networkError);
return;
}
emit jobFinished(reply->readAll()); emit jobFinished(reply->readAll());
} }
} }

View file

@ -15,6 +15,10 @@
#ifndef ICONJOB_H #ifndef ICONJOB_H
#define ICONJOB_H #define ICONJOB_H
#include "account.h"
#include "accountfwd.h"
#include "owncloudlib.h"
#include <QObject> #include <QObject>
#include <QByteArray> #include <QByteArray>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
@ -27,20 +31,18 @@ namespace OCC {
* @brief Job to fetch a icon * @brief Job to fetch a icon
* @ingroup gui * @ingroup gui
*/ */
class IconJob : public QObject class OWNCLOUDSYNC_EXPORT IconJob : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit IconJob(const QUrl &url, QObject *parent = nullptr); explicit IconJob(AccountPtr account, const QUrl &url, QObject *parent = nullptr);
signals: signals:
void jobFinished(QByteArray iconData); void jobFinished(QByteArray iconData);
void error(QNetworkReply::NetworkError errorType);
private slots: private slots:
void finished(QNetworkReply *reply); void finished();
private:
QNetworkAccessManager _accessManager;
}; };
} }

View file

@ -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 <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QLoggingCategory>
#include <QIcon>
#include <QPainter>
#include <QImage>
#include <QSvgRenderer>
#include <QNetworkReply>
#include <QPixmap>
#include <QPixmapCache>
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<QPixmap> 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<QPixmap> iconDataToPixmap(const QByteArray iconData)
{
if (!iconData.startsWith("<svg")) {
return {};
}
return createPixmapFromSvgData(iconData);
}
}
namespace OCC {
HovercardAction::HovercardAction() = default;
HovercardAction::HovercardAction(QString title, QUrl iconUrl, QUrl link)
: _title(std::move(title))
, _iconUrl(std::move(iconUrl))
, _link(std::move(link))
{
}
OcsProfileConnector::OcsProfileConnector(AccountPtr account, QObject *parent)
: QObject(parent)
, _account(account)
{
}
void OcsProfileConnector::fetchHovercard(const QString &userId)
{
if (_account->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{_account, 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;
}
}

View file

@ -0,0 +1,58 @@
#pragma once
#include "accountfwd.h"
#include "owncloudlib.h"
#include <QObject>
#include <QPixmap>
#include <QUrl>
#include <QString>
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<HovercardAction> _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)