Add profile page
authorFelix Weilbach <felix.weilbach@nextcloud.com>
Fri, 22 Oct 2021 09:16:38 +0000 (11:16 +0200)
committerFelix Weilbach <felix.weilbach@nextcloud.com>
Wed, 10 Nov 2021 12:11:47 +0000 (13:11 +0100)
Fix: #3889

Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
13 files changed:
src/gui/CMakeLists.txt
src/gui/iconjob.cpp [deleted file]
src/gui/iconjob.h [deleted file]
src/gui/profilepagewidget.cpp [new file with mode: 0644]
src/gui/profilepagewidget.h [new file with mode: 0644]
src/gui/shareusergroupwidget.cpp
src/gui/shareusergroupwidget.h
src/gui/shareuserline.ui
src/libsync/CMakeLists.txt
src/libsync/iconjob.cpp [new file with mode: 0644]
src/libsync/iconjob.h [new file with mode: 0644]
src/libsync/ocsprofileconnector.cpp [new file with mode: 0644]
src/libsync/ocsprofileconnector.h [new file with mode: 0644]

index c023f23576e3e01d2e1ac5f86ed47fa0e8e9551e..3cbca8dbf29d9f7e2334a6d7fd5003320f569f74 100644 (file)
@@ -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 (file)
index 2eaed5d..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) by Camila Ayres <hello@camila.codes>
- *
- * 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 (file)
index efbbeb3..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) by Camila Ayres <hello@camila.codes>
- *
- * 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 <QObject>
-#include <QByteArray>
-#include <QNetworkAccessManager>
-#include <QNetworkRequest>
-#include <QNetworkReply>
-
-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 (file)
index 0000000..5bed47d
--- /dev/null
@@ -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);
+}
+}
diff --git a/src/gui/profilepagewidget.h b/src/gui/profilepagewidget.h
new file mode 100644 (file)
index 0000000..637dbd4
--- /dev/null
@@ -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;
+};
+}
index 0c37c4565e7fde2c5e1f055b891f3ca2eed57c60..9c9bc3fc06275b6102d56da6b2af5bfff461a49b 100644 (file)
@@ -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 <QFileInfo>
 #include <QAbstractProxyModel>
 #include <QCompleter>
-#include <qlayout.h>
+#include <QBoxLayout>
+#include <QIcon>
+#include <QLayout>
 #include <QPropertyAnimation>
 #include <QMenu>
 #include <QAction>
 #include <QPainter>
 #include <QListWidget>
 #include <QSvgRenderer>
+#include <QPushButton>
+#include <QContextMenuEvent>
 
 #include <cstring>
 
 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<QContextMenuEvent *>(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<UserGroupShare> share,
-                             SharePermissions maxSharingPermissions,
-                             bool isFile,
-                             QWidget *parent)
+ShareUserLine::ShareUserLine(AccountPtr account, QSharedPointer<UserGroupShare> 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;
index c63e56d74b0df1b4f85f1cf479af9c1a0e9807b8..599ab521b55751bfad2361b27abc3bc4e60ff2ec 100644 (file)
@@ -19,6 +19,7 @@
 #include "sharemanager.h"
 #include "sharepermissions.h"
 #include "sharee.h"
+#include "profilepagewidget.h"
 #include "QProgressIndicator.h"
 #include <QDialog>
 #include <QWidget>
@@ -26,6 +27,7 @@
 #include <QList>
 #include <QVector>
 #include <QTimer>
+#include <qpushbutton.h>
 #include <qscrollarea.h>
 
 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<UserGroupShare> _share;
   bool _isFile;
 
+  ProfilePageMenu _profilePageMenu;
+
   // _permissionEdit is a checkbox
   QAction *_permissionReshare;
   QAction *_deleteShareButton;
index e79614a06bc0aa2e340d7cc820aa9477e1298f81..5067a31201dd8794362a0fb93351c8e033b8ba62 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>980</width>
-    <height>239</height>
+    <width>899</width>
+    <height>310</height>
    </rect>
   </property>
   <property name="sizePolicy">
         <palette>
          <active>
           <colorrole role="WindowText">
+           <brush brushstyle="SolidPattern">
+            <color alpha="200">
+             <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>
+             <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="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>
          <inactive>
           <colorrole role="WindowText">
+           <brush brushstyle="SolidPattern">
+            <color alpha="200">
+             <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>
+             <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="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>
          <disabled>
           <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">
+            <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>
+             <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>123</red>
-             <green>121</green>
-             <blue>134</blue>
+             <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>
index 0ec0ccc0ea1e6546caa45fd7918c0583faef81cf..d58e27d218e438f697b50dd3d0d6327c00714b4d 100644 (file)
@@ -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 (file)
index 0000000..0c90171
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) by Camila Ayres <hello@camila.codes>
+ *
+ * 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 (file)
index 0000000..719ea3b
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) by Camila Ayres <hello@camila.codes>
+ *
+ * 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 <QObject>
+#include <QByteArray>
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+
+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 (file)
index 0000000..5bda93c
--- /dev/null
@@ -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{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 (file)
index 0000000..69cd601
--- /dev/null
@@ -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)