Do not display notifications when user status is do not disturb.
authorCamila <hello@camila.codes>
Tue, 16 Mar 2021 19:24:11 +0000 (20:24 +0100)
committerCamila <hello@camila.codes>
Wed, 24 Mar 2021 14:40:05 +0000 (15:40 +0100)
- This information is retrieved from the notifications endpoint.
- Add icons for the different pre defined status.
- Make functions available to QML to decide which status icon to display.
- Display the user status icon on the avatar and
move the online/offline connection status to the folder icon.

Signed-off-by: Camila <hello@camila.codes>
20 files changed:
src/gui/accountstate.cpp
src/gui/accountstate.h
src/gui/tray/NotificationHandler.cpp
src/gui/tray/NotificationHandler.h
src/gui/tray/UserLine.qml
src/gui/tray/UserModel.cpp
src/gui/tray/UserModel.h
src/gui/tray/Window.qml
src/gui/userstatus.cpp
src/gui/userstatus.h
src/libsync/networkjobs.cpp
src/libsync/networkjobs.h
src/libsync/theme.cpp
src/libsync/theme.h
theme.qrc
theme/Style/Style.qml
theme/colored/user-status-away.svg [new file with mode: 0644]
theme/colored/user-status-dnd.svg [new file with mode: 0644]
theme/colored/user-status-invisible.svg [new file with mode: 0644]
theme/colored/user-status-online.svg [new file with mode: 0644]

index f78dc5f3765be692015b870c0216e92055003ed0..bd548c6a8c8538a4e3c4955a5f872131adfdf958 100644 (file)
@@ -45,7 +45,8 @@ AccountState::AccountState(AccountPtr account)
     , _waitingForNewCredentials(false)
     , _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
     , _remoteWipe(new RemoteWipe(_account))
-    , _userStatus(new UserStatus(this, this))
+    , _userStatus(new UserStatus(this))
+    , _notificationStatus("online")
 {
     qRegisterMetaType<AccountState *>("AccountState*");
 
@@ -127,9 +128,19 @@ void AccountState::setState(State state)
     emit stateChanged(_state);
 }
 
-QString AccountState::currentUserStatus() const
+QString AccountState::status() const
 {
-    return _userStatus->currentUserStatus();
+    return _userStatus->status();
+}
+
+QString AccountState::statusMessage() const
+{
+    return _userStatus->message();
+}
+
+QUrl AccountState::statusIcon() const
+{
+    return _userStatus->icon();
 }
 
 QString AccountState::stateString(State state)
@@ -212,6 +223,16 @@ void AccountState::setNavigationAppsEtagResponseHeader(const QByteArray &value)
     _navigationAppsEtagResponseHeader = value;
 }
 
+QString AccountState::notificationStatus() const
+{
+    return _notificationStatus;
+}
+
+void AccountState::setNotificationStatus(const QString &status)
+{
+    _notificationStatus = status;
+}
+
 void AccountState::checkConnectivity()
 {
     if (isSignedOut() || _waitingForNewCredentials) {
@@ -429,8 +450,10 @@ void AccountState::fetchNavigationApps(){
     job->getNavigationApps();
 }
 
-void AccountState::fetchCurrentUserStatus() {
-    _userStatus->fetchCurrentUserStatus();
+void AccountState::fetchUserStatus() 
+{
+    connect(_userStatus, &UserStatus::fetchUserStatusFinished, this, &AccountState::userStatusChanged);
+    _userStatus->fetchUserStatus(_account);
 }
 
 void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
index d31ebc7d80b90d0a29d99853ba0248b5dfde31e8..5995449063980e29c1ae79ad240fac74c89df9cd 100644 (file)
@@ -162,9 +162,31 @@ public:
     ///Asks for user credentials
     void handleInvalidCredentials();
 
-    QString currentUserStatus() const;
+    /** Returns the user status (online, dnd, away, offline, invisible)
+     *  https://gist.github.com/georgehrke/55a0412007f13be1551d1f9436a39675
+    */
+    QString status() const;
+
+    /** Returns the user status Message (emoji + text)
+    */
+    QString statusMessage() const;
+
+    /** Returns the user status icon url
+    */
+    QUrl statusIcon() const;
 
-    void fetchCurrentUserStatus();
+    /** Returns the user status retrieved by the notificatons endpoint: dnd or online
+     *  https://github.com/nextcloud/desktop/issues/2318#issuecomment-680698429
+    */
+    QString notificationStatus() const;
+
+    /** Set new user status retrieved by the notificatons endpoint: dnd or online
+    */
+    void setNotificationStatus(const QString &status);
+
+    /** Fetch the user status (status, icon, message)
+    */
+    void fetchUserStatus();
 
 public slots:
     /// Triggers a ping to the server to update state and
@@ -230,6 +252,7 @@ private:
     AccountAppList _apps;
 
     UserStatus *_userStatus;
+    QString _notificationStatus;
 };
 
 class AccountApp : public QObject
index 98e5c1aa7875a0f1c2e262b826f9f48a072a4d1d..4207ef1b778282bb00210d1427e2e98e57ba5b8a 100644 (file)
@@ -48,6 +48,8 @@ void ServerNotificationHandler::slotFetchNotifications()
         this, &ServerNotificationHandler::slotNotificationsReceived);
     QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived,
         this, &ServerNotificationHandler::slotEtagResponseHeaderReceived);
+    QObject::connect(_notificationJob.data(), &JsonApiJob::desktopNotificationStatusReceived,
+            this, &ServerNotificationHandler::slotDesktopNotificationStatusReceived);
     _notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue<AccountState *>(_accountState));
     _notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader());
     _notificationJob->start();
@@ -62,6 +64,14 @@ void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray
     }
 }
 
+void ServerNotificationHandler::slotDesktopNotificationStatusReceived(const bool status)
+{
+    auto *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
+    if (account != nullptr) {
+       account->setDesktopNotificationsStatus(status);
+    }
+}
+
 void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData)
 {
     iconCache.insert(sender()->property("activityId").toInt(),iconData);
index 69e286e783e337b05b25ce3b23060258e61ef7a3..a070ab6caf30543fbe61b5d2ae8b43aadea9d32b 100644 (file)
@@ -26,6 +26,7 @@ private slots:
     void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
     void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
     void slotIconDownloaded(QByteArray iconData);
+    void slotDesktopNotificationStatusReceived(const bool status);
 
 private:
     QPointer<JsonApiJob> _notificationJob;
index 069bccf2ab254fcdad7348dcc6feab0eb13dcf0a..ce752fef1cbe1e43d0b601bfc233f7aa30877629 100644 (file)
@@ -35,7 +35,7 @@ MenuItem {
                     anchors.fill: parent\r
                     hoverEnabled: true\r
                     onContainsMouseChanged: {\r
-                        accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")\r
+                        accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")\r
                     }\r
                     onClicked: {\r
                         if (!isCurrentUser) {\r
@@ -71,8 +71,8 @@ MenuItem {
                         Layout.preferredHeight: (userLineLayout.height -16)\r
                         Layout.preferredWidth: (userLineLayout.height -16)\r
                         Rectangle {\r
-                            id: accountStateIndicatorBackground\r
-                            width: accountStateIndicator.sourceSize.width + 2\r
+                            id: accountStatusIndicatorBackground\r
+                            width: accountStatusIndicator.sourceSize.width + 2\r
                             height: width\r
                             anchors.bottom: accountAvatar.bottom\r
                             anchors.right: accountAvatar.right\r
@@ -80,18 +80,16 @@ MenuItem {
                             radius: width*0.5\r
                         }\r
                         Image {\r
-                            id: accountStateIndicator\r
-                            source: model.isConnected\r
-                                    ? Style.stateOnlineImageSource\r
-                                    : Style.stateOfflineImageSource\r
+                            id: accountStatusIndicator\r
+                            source: model.statusIcon\r
                             cache: false\r
-                            x: accountStateIndicatorBackground.x + 1\r
-                            y: accountStateIndicatorBackground.y + 1\r
+                            x: accountStatusIndicatorBackground.x + 1\r
+                            y: accountStatusIndicatorBackground.y + 1\r
                             sourceSize.width: Style.accountAvatarStateIndicatorSize\r
                             sourceSize.height: Style.accountAvatarStateIndicatorSize\r
 \r
                             Accessible.role: Accessible.Indicator\r
-                            Accessible.name: model.isConnected ? qsTr("Account connected") : qsTr("Account not connected")\r
+                            Accessible.name: model.isStatusOnline ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")\r
                         }\r
                     }\r
 \r
@@ -110,9 +108,9 @@ MenuItem {
                             font.bold: true\r
                         }\r
                         Label {\r
-                            id: userStatus\r
+                            id: userStatusMessage\r
                             width: 128\r
-                            text: status\r
+                            text: statusMessage\r
                             elide: Text.ElideRight\r
                             color: "black"\r
                             font.pixelSize: 10\r
@@ -231,13 +229,4 @@ MenuItem {
                 }\r
             }\r
         }\r
-\r
-        Connections {\r
-            target: UserModel\r
-            onRefreshCurrentUserGui: {\r
-                accountStateIndicator.source = model.isConnected\r
-                        ? Style.stateOnlineImageSource\r
-                        : Style.stateOfflineImageSource\r
-            }\r
-        }\r
 }   // MenuItem userLine\r
index 54ac107c62e4cf22569f15643667207b1d13068d..8ca2c2aa61c60941926076d69048ceac8e231913 100644 (file)
@@ -88,7 +88,7 @@ void User::slotBuildNotificationDisplay(const ActivityList &list)
 
             // Assemble a tray notification for the NEW notification
             ConfigFile cfg;
-            if (cfg.optionalServerNotifications() /*and header is not X-Nextcloud-User-Status*/) {
+            if (cfg.optionalServerNotifications() && isDesktopNotificationsAllowed()) {
                 if (AccountManager::instance()->accounts().count() == 1) {
                     emit guiLog(activity._subject, "");
                 } else {
@@ -208,7 +208,7 @@ void User::slotRefresh()
             slotRefreshActivities();
         }
         slotRefreshNotifications();
-        _account.data()->fetchCurrentUserStatus();
+        _account.data()->fetchUserStatus();
         timer.start();
     }
 }
@@ -559,9 +559,19 @@ QString User::server(bool shortened) const
     return serverUrl;
 }
 
-QString User::currentUserStatus() const
+QString User::status() const
 {
-    return _account->currentUserStatus();
+    return _account->status();
+}
+
+QString User::statusMessage() const
+{
+    return _account->statusMessage();
+}
+
+QUrl User::statusIcon() const
+{
+    return _account->statusIcon();
 }
 
 QImage User::avatar() const
@@ -613,6 +623,12 @@ bool User::isConnected() const
     return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected);
 }
 
+
+bool User::isDesktopNotificationsAllowed() const
+{
+    return _account.data()->notificationStatus() == "online";
+}
+
 void User::removeAccount() const
 {
     AccountManager::instance()->deleteAccount(_account.data());
@@ -674,6 +690,16 @@ Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
     return _users[id]->isConnected();
 }
 
+Q_INVOKABLE QUrl UserModel::statusIcon(const int &id)
+{
+    if (id < 0 || id >= _users.size()) {
+        return {};
+    }
+
+    return _users[id]->statusIcon();
+}
+
+
 QImage UserModel::avatarById(const int &id)
 {
     if (id < 0 || id >= _users.size())
@@ -711,7 +737,8 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
         });
 
         connect(u, &User::userStatusChanged, this, [this, row] {
-            emit dataChanged(index(row, 0), index(row, 0), {UserModel::StatusRole});
+            emit dataChanged(index(row, 0), index(row, 0), {UserModel::StatusIconRole, 
+                                                            UserModel::StatusMessageRole});
         });
 
         _users << u;
@@ -852,8 +879,10 @@ QVariant UserModel::data(const QModelIndex &index, int role) const
         return _users[index.row()]->name();
     } else if (role == ServerRole) {
         return _users[index.row()]->server();
-    } else if (role == StatusRole) {
-        return _users[index.row()]->currentUserStatus();
+    } else if (role == StatusIconRole) {
+        return _users[index.row()]->statusIcon();
+    } else if (role == StatusMessageRole) {
+        return _users[index.row()]->statusMessage();
     } else if (role == AvatarRole) {
         return _users[index.row()]->avatarUrl();
     } else if (role == IsCurrentUserRole) {
@@ -871,7 +900,8 @@ QHash<int, QByteArray> UserModel::roleNames() const
     QHash<int, QByteArray> roles;
     roles[NameRole] = "name";
     roles[ServerRole] = "server";
-    roles[StatusRole] = "status";
+    roles[StatusIconRole] = "statusIcon";
+    roles[StatusMessageRole] = "statusMessage";
     roles[AvatarRole] = "avatar";
     roles[IsCurrentUserRole] = "isCurrentUser";
     roles[IsConnectedRole] = "isConnected";
index 1d439d49bca517a508a993d3b5c6eccd6eb9ce71..a737633bd6dcb50e5194c250ec0881d3c12cf67c 100644 (file)
@@ -19,7 +19,8 @@ class User : public QObject
     Q_OBJECT
     Q_PROPERTY(QString name READ name NOTIFY nameChanged)
     Q_PROPERTY(QString server READ server CONSTANT)
-    Q_PROPERTY(QString status READ currentUserStatus NOTIFY userStatusChanged)
+    Q_PROPERTY(QUrl statusIcon READ statusIcon NOTIFY userStatusChanged)
+    Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY userStatusChanged)
     Q_PROPERTY(bool hasLocalFolder READ hasLocalFolder NOTIFY hasLocalFolderChanged)
     Q_PROPERTY(bool serverHasTalk READ serverHasTalk NOTIFY serverHasTalkChanged)
     Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
@@ -36,7 +37,6 @@ public:
     void openLocalFolder();
     QString name() const;
     QString server(bool shortened = true) const;
-    QString currentUserStatus() const;
     bool hasLocalFolder() const;
     bool serverHasTalk() const;
     AccountApp *talkApp() const;
@@ -47,6 +47,10 @@ public:
     void logout() const;
     void removeAccount() const;
     QString avatarUrl() const;
+    bool isDesktopNotificationsAllowed() const;
+    QString status() const;
+    QString statusMessage() const;
+    QUrl statusIcon() const;
 
 signals:
     void guiLog(const QString &, const QString &);
@@ -135,6 +139,7 @@ public:
     Q_INVOKABLE bool currentUserHasLocalFolder();
     int currentUserId() const;
     Q_INVOKABLE bool isUserConnected(const int &id);
+    Q_INVOKABLE QUrl statusIcon(const int &id);
     Q_INVOKABLE void switchCurrentUser(const int &id);
     Q_INVOKABLE void login(const int &id);
     Q_INVOKABLE void logout(const int &id);
@@ -145,7 +150,8 @@ public:
     enum UserRoles {
         NameRole = Qt::UserRole + 1,
         ServerRole,
-        StatusRole,
+        StatusIconRole,
+        StatusMessageRole,
         AvatarRole,
         IsCurrentUserRole,
         IsConnectedRole,
index 2a6f39db30c950a295177c2b32d8dbd2d835158c..f27bdc3505eb46e263979ab47212a2050b857f2d 100644 (file)
@@ -35,8 +35,8 @@ Window {
     }\r
 \r
     onVisibleChanged: {\r
-        currentAccountStateIndicator.source = ""\r
-        currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)\r
+        folderStateIndicator.source = ""\r
+        folderStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)\r
                 ? Style.stateOnlineImageSource\r
                 : Style.stateOfflineImageSource\r
 \r
@@ -49,8 +49,8 @@ Window {
     Connections {\r
         target: UserModel\r
         onRefreshCurrentUserGui: {\r
-            currentAccountStateIndicator.source = ""\r
-            currentAccountStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)\r
+            folderStateIndicator.source = ""\r
+            folderStateIndicator.source = UserModel.isUserConnected(UserModel.currentUserId)\r
                     ? Style.stateOnlineImageSource\r
                     : Style.stateOfflineImageSource\r
         }\r
@@ -328,7 +328,7 @@ Window {
                             Accessible.name: qsTr("Current user avatar")\r
 \r
                             Rectangle {\r
-                                id: currentAccountStateIndicatorBackground\r
+                                id: currentAccountStatusIndicatorBackground\r
                                 width: Style.accountAvatarStateIndicatorSize + 2\r
                                 height: width\r
                                 anchors.bottom: currentAccountAvatar.bottom\r
@@ -348,18 +348,16 @@ Window {
                             }\r
 \r
                             Image {\r
-                                id: currentAccountStateIndicator\r
-                                source: UserModel.isUserConnected(UserModel.currentUserId)\r
-                                        ? Style.stateOnlineImageSource\r
-                                        : Style.stateOfflineImageSource\r
+                                id: currentAccountStatusIndicator\r
+                                source: UserModel.currentUser.statusIcon\r
                                 cache: false\r
-                                x: currentAccountStateIndicatorBackground.x + 1\r
-                                y: currentAccountStateIndicatorBackground.y + 1\r
+                                x: currentAccountStatusIndicatorBackground.x + 1\r
+                                y: currentAccountStatusIndicatorBackground.y + 1\r
                                 sourceSize.width: Style.accountAvatarStateIndicatorSize\r
                                 sourceSize.height: Style.accountAvatarStateIndicatorSize\r
 \r
                                 Accessible.role: Accessible.Indicator\r
-                                Accessible.name: UserModel.isUserConnected(UserModel.currentUserId()) ? qsTr("Connected") : qsTr("Disconnected")\r
+                                Accessible.name: UserModel.isUserStatusOnline(UserModel.currentUserId()) ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")\r
                             }\r
                         }\r
 \r
@@ -381,7 +379,7 @@ Window {
                             Label {\r
                                 id: currentUserStatus\r
                                 width: Style.currentAccountLabelWidth\r
-                                text: UserModel.currentUser.status\r
+                                text: UserModel.currentUser.statusMessage\r
                                 elide: Text.ElideRight\r
                                 color: Style.ncTextColor\r
                                 font.pixelSize: Style.subLinePixelSize\r
@@ -423,6 +421,42 @@ Window {
                     Accessible.onPressAction: openLocalFolderButton.clicked()\r
                 }\r
 \r
+                Rectangle {\r
+                    id: folderStateIndicatorBackground\r
+                    width: Style.folderStateIndicatorSize\r
+                    height: width\r
+                    anchors.top: openLocalFolderButton.verticalCenter\r
+                    anchors.left: openLocalFolderButton.horizontalCenter\r
+                    color: Style.ncBlue\r
+                    radius: width*0.5\r
+                }\r
+\r
+                Rectangle {\r
+                    id: folderStateRectangle\r
+                    width: Style.folderStateIndicatorSize\r
+                    height: width\r
+                    anchors.bottom: openLocalFolderButton.bottom\r
+                    anchors.right: openLocalFolderButton.right\r
+                    color: openLocalFolderButton.containsMouse ? "white" : "transparent"\r
+                    opacity: 0.2\r
+                    radius: width*0.5\r
+                }\r
+\r
+                Image {\r
+                    id: folderStateIndicator\r
+                    source: UserModel.isUserConnected(UserModel.currentUserId)\r
+                            ? Style.stateOnlineImageSource\r
+                            : Style.stateOfflineImageSource\r
+                    cache: false\r
+                    x: folderStateIndicatorBackground.x\r
+                    y: folderStateIndicatorBackground.y\r
+                    sourceSize.width: Style.folderStateIndicatorSize\r
+                    sourceSize.height: Style.folderStateIndicatorSize\r
+\r
+                    Accessible.role: Accessible.Indicator\r
+                    Accessible.name: UserModel.isUserConnected(UserModel.currentUserId()) ? qsTr("Connected") : qsTr("Disconnected")\r
+                }\r
+\r
                 HeaderButton {\r
                     id: trayWindowTalkButton\r
 \r
index 4a678ae739b9f0bbf8f577a971ccac4c0c7d61dd..7b8c7d54208197c4ad6f8f6e929d5b6750ac211d 100644 (file)
@@ -18,7 +18,7 @@
 #include "networkjobs.h"
 #include "folderman.h"
 #include "creds/abstractcredentials.h"
-#include <theme.h>
+#include "theme.h"
 
 #include <QTimer>
 #include <QJsonDocument>
 
 namespace OCC {
 
-UserStatus::UserStatus(AccountState *accountState, QObject *parent)
+UserStatus::UserStatus(QObject *parent)
     : QObject(parent)
-    , _accountState(accountState)
+    , _status("online")
+    , _message("")
 {
-    connect(this, &UserStatus::fetchedCurrentUserStatus, _accountState, &AccountState::userStatusChanged);
+
 }
 
-void UserStatus::fetchCurrentUserStatus()
+void UserStatus::fetchUserStatus(AccountPtr account)
 {
     if (_job) {
         _job->deleteLater();
     }
 
-    AccountPtr account = _accountState->account();
     _job = new JsonApiJob(account, QStringLiteral("/ocs/v2.php/apps/user_status/api/v1/user_status"), this);
-    connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchedCurrentStatus);
+    connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchUserStatusFinished);
     _job->start();
 }
 
-void UserStatus::slotFetchedCurrentStatus(const QJsonDocument &json)
+void UserStatus::slotFetchUserStatusFinished(const QJsonDocument &json)
 {
     const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject();
-    const auto icon = retrievedData.value("icon").toString();
+    const auto emoji = retrievedData.value("icon").toString();
     const auto message = retrievedData.value("message").toString();
-    auto status = retrievedData.value("status").toString();
+    _status = retrievedData.value("status").toString();
 
-    if(message.isEmpty()) {
-        if(status == "dnd") {
-            status = tr("Do not disturb");
-        }
-    } else {
-        status = message;
-    }
+    const auto visibleStatusText = message.isEmpty()
+                                ? _status == "dnd"? tr("Do not disturb") : _status
+                                : message;
+
+    _message = QString("%1 %2").arg(emoji, visibleStatusText);
+    emit fetchUserStatusFinished();
+}
+
+QString UserStatus::status() const
+{
+    return _status;
+}
 
-    _currentUserStatus = QString("%1 %2").arg(icon, status);
-    emit fetchedCurrentUserStatus();
+QString UserStatus::message() const
+{
+    return _message;
 }
 
-QString UserStatus::currentUserStatus() const
+QUrl UserStatus::icon() const
 {
-    return _currentUserStatus;
+    // online, away, dnd, invisible, offline
+    if(_status == "online") {
+        return Theme::instance()->statusOnlineImageSource();
+    } else if (_status == "away") {
+        return Theme::instance()->statusAwayImageSource();
+    } else if (_status == "dnd") {
+        return Theme::instance()->statusDoNotDisturbImageSource();
+    } else if (_status == "invisible") {
+        return Theme::instance()->statusInvisibleImageSource();
+    } else if (_status == "offline") {
+        return Theme::instance()->statusInvisibleImageSource();
+    }
+
+    return Theme::instance()->statusOnlineImageSource();
 }
 
 } // namespace OCC
index 4fa9eabad088538f843a26dde519c39cd7d3285b..d723584b531594d96f933a58d0faba101543bcd2 100644 (file)
 
 #include <QObject>
 #include <QPointer>
-#include <QVariant>
+#include "accountfwd.h"
 
 namespace OCC {
-class AccountState;
 class JsonApiJob;
 
 class UserStatus : public QObject
@@ -28,20 +27,22 @@ class UserStatus : public QObject
     Q_OBJECT
 
 public:
-    explicit UserStatus(AccountState *accountState, QObject *parent = nullptr);
-    void fetchCurrentUserStatus();
-    QString currentUserStatus() const;
+    explicit UserStatus(QObject *parent = nullptr);
+    void fetchUserStatus(AccountPtr account);
+    QString status() const;
+    QString message() const;
+    QUrl icon() const;
 
 private slots:
-    void slotFetchedCurrentStatus(const QJsonDocument &json);
+    void slotFetchUserStatusFinished(const QJsonDocument &json);
 
 signals:
-    void fetchedCurrentUserStatus();
+    void fetchUserStatusFinished();
 
 private:
-    QPointer<AccountState> _accountState;
     QPointer<JsonApiJob> _job; // the currently running job
-    QString _currentUserStatus;
+    QString _status;
+    QString _message;
 };
 
 
index f701e2dc1c94d206c0fa80602cc59d9d285a49a2..93b4936b2dd8e38a50fadea83e1974f37bef5eeb 100644 (file)
@@ -871,6 +871,11 @@ bool JsonApiJob::finished()
     if(reply()->rawHeaderList().contains("ETag"))
         emit etagResponseHeaderReceived(reply()->rawHeader("ETag"), statusCode);
 
+    const auto desktopNotificationStatus = reply()->rawHeader(QByteArray("X-Nextcloud-User-Status"));
+    if(!desktopNotificationStatus.isEmpty()) {
+        emit desktopNotificationStatusReceived(desktopNotificationStatus == "online");
+    }
+
     QJsonParseError error;
     auto json = QJsonDocument::fromJson(jsonStr.toUtf8(), &error);
     // empty or invalid response and status code is != 304 because jsonStr is expected to be empty
index c9fd92fb2879277751ee73537ce06f5f17f79a75..e67eded8f886350f97df47fc35aa3b491bf19c53 100644 (file)
@@ -420,6 +420,12 @@ signals:
      * @param statusCode - the OCS status code: 100 (!) for success
      */
     void etagResponseHeaderReceived(const QByteArray &value, int statusCode);
+    
+    /**
+     * @brief desktopNotificationStatusReceived - signal to report the if user is online or dnd
+     * @param status - set desktop notifications allowed status 
+     */
+    void desktopNotificationStatusReceived(const bool status);
 
 private:
     QUrlQuery _additionalParams;
index b77a6665593e7333c75c50df772abec208b9f070..c94933cca7b80271ffbb389c25def36e559e51b6 100644 (file)
@@ -139,6 +139,26 @@ QUrl Theme::stateOfflineImageSource() const
     return imagePathToUrl(themeImagePath("state-offline", 16));
 }
 
+QUrl Theme::statusOnlineImageSource() const
+{
+    return imagePathToUrl(themeImagePath("user-status-online", 16));
+}
+
+QUrl Theme::statusDoNotDisturbImageSource() const
+{
+    return imagePathToUrl(themeImagePath("user-status-dnd", 16));
+}
+
+QUrl Theme::statusAwayImageSource() const
+{
+    return imagePathToUrl(themeImagePath("user-status-away", 16));
+}
+
+QUrl Theme::statusInvisibleImageSource() const
+{
+    return imagePathToUrl(themeImagePath("user-status-invisible", 16));
+}
+
 QString Theme::version() const
 {
     return MIRALL_VERSION_STRING;
index a06828b1fcb40c7a8f7420432845679deb22cfd2..7dcdb55bf0d5614396d03364748e6b3838cc9014 100644 (file)
@@ -42,6 +42,11 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject
     Q_PROPERTY(QString appName READ appName CONSTANT)
     Q_PROPERTY(QUrl stateOnlineImageSource READ stateOnlineImageSource CONSTANT)
     Q_PROPERTY(QUrl stateOfflineImageSource READ stateOfflineImageSource CONSTANT)
+    Q_PROPERTY(QUrl stateOnlineImageSource READ stateOnlineImageSource CONSTANT)
+    Q_PROPERTY(QUrl statusOnlineImageSource READ statusOnlineImageSource CONSTANT)
+    Q_PROPERTY(QUrl statusDoNotDisturbImageSource READ statusDoNotDisturbImageSource CONSTANT)
+    Q_PROPERTY(QUrl statusAwayImageSource READ statusAwayImageSource CONSTANT)
+    Q_PROPERTY(QUrl statusInvisibleImageSource READ statusInvisibleImageSource CONSTANT)
 #ifndef TOKEN_AUTH_ONLY
     Q_PROPERTY(QIcon folderDisabledIcon READ folderDisabledIcon CONSTANT)
     Q_PROPERTY(QIcon folderOfflineIcon READ folderOfflineIcon CONSTANT)
@@ -122,6 +127,30 @@ public:
      * @return QUrl full path to an icon
      */
     QUrl stateOfflineImageSource() const;
+    
+    /**
+     * @brief Returns full path to an online user status icon
+     * @return QUrl full path to an icon
+     */
+    QUrl statusOnlineImageSource() const;
+    
+    /**
+     * @brief Returns full path to an do not disturb user status icon
+     * @return QUrl full path to an icon
+     */
+    QUrl statusDoNotDisturbImageSource() const;
+    
+    /**
+     * @brief Returns full path to an away user status icon
+     * @return QUrl full path to an icon
+     */
+    QUrl statusAwayImageSource() const;
+    
+    /**
+     * @brief Returns full path to an invisible user status icon
+     * @return QUrl full path to an icon
+     */
+    QUrl statusInvisibleImageSource() const;
 
     /**
      * @brief configFileName
index 620a406027b9810a0729179013f2153cb6e5f228..4402bd2c2c970717697ecb255d2657a32374d8b3 100644 (file)
--- a/theme.qrc
+++ b/theme.qrc
         <file>theme/share.svg</file>
         <file>theme/reply.svg</file>
         <file>theme/magnifying-glass.svg</file>
+        <file>theme/colored/user-status-online.svg</file>
+        <file>theme/colored/user-status-invisible.svg</file>
+        <file>theme/colored/user-status-away.svg</file>
+        <file>theme/colored/user-status-dnd.svg</file>
     </qresource>
 </RCC>
index 332d8133dfc614cc09d5a97e3db8b7b1c73b3cb9..520de312dd75b0840d5d355ce81852f23165c165 100644 (file)
@@ -33,6 +33,7 @@ QtObject {
 \r
     property int accountAvatarSize: (trayWindowHeaderHeight - 16)\r
     property int accountAvatarStateIndicatorSize: 16\r
+    property int folderStateIndicatorSize: 16\r
     property int accountLabelWidth: 128\r
 \r
     property int accountDropDownCaretSize: 20\r
diff --git a/theme/colored/user-status-away.svg b/theme/colored/user-status-away.svg
new file mode 100644 (file)
index 0000000..775961d
--- /dev/null
@@ -0,0 +1 @@
+<svg width="24" height="24" enable-background="new 0 0 24 24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" fill="none"/><path d="m10.615 2.1094c-4.8491 0.68106-8.6152 4.8615-8.6152 9.8906 0 5.5 4.5 10 10 10 5.0292 0 9.2096-3.7661 9.8906-8.6152-1.4654 1.601-3.5625 2.6152-5.8906 2.6152-4.4 0-8-3.6-8-8 0-2.3281 1.0143-4.4252 2.6152-5.8906z" fill="#f4a331"/></svg>
\ No newline at end of file
diff --git a/theme/colored/user-status-dnd.svg b/theme/colored/user-status-dnd.svg
new file mode 100644 (file)
index 0000000..3b44ca2
--- /dev/null
@@ -0,0 +1 @@
+<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10z" fill="#ed484c"/><path d="m8 10h8c1.108 0 2 0.892 2 2s-0.892 2-2 2h-8c-1.108 0-2-0.892-2-2s0.892-2 2-2z" fill="#fdffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" style="paint-order:stroke markers fill"/></svg>
\ No newline at end of file
diff --git a/theme/colored/user-status-invisible.svg b/theme/colored/user-status-invisible.svg
new file mode 100644 (file)
index 0000000..d69bd69
--- /dev/null
@@ -0,0 +1 @@
+<svg width="24" height="24" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="m12 2c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10zm0 4a6 6 0 0 1 6 6 6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 6-6z" fill="#000"/></svg>
\ No newline at end of file
diff --git a/theme/colored/user-status-online.svg b/theme/colored/user-status-online.svg
new file mode 100644 (file)
index 0000000..95c807a
--- /dev/null
@@ -0,0 +1 @@
+<svg width="24" height="24" enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m8 16h8v-8h-8v8zm4-14c-5.52 0-10 4.48-10 10s4.48 10 10 10 10-4.48 10-10-4.48-10-10-10z" fill="#49B382"/></svg>
\ No newline at end of file