Skip to content

Commit 50a6b62

Browse files
committed
Add account based notifications
1 parent 36fccb7 commit 50a6b62

File tree

9 files changed

+265
-6
lines changed

9 files changed

+265
-6
lines changed

src/gui/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ target_sources(OpenCloudGui PRIVATE
4242
logbrowser.cpp
4343
networkinformation.cpp
4444
networksettings.cpp
45+
notifications.cpp
4546
openfilemanager.cpp
4647
owncloudgui.cpp
4748
protocolwidget.cpp
@@ -100,6 +101,8 @@ ecm_add_qml_module(OpenCloudGui
100101
qml/FolderDelegate.qml
101102
qml/FolderError.qml
102103

104+
qml/Notifications.qml
105+
103106
qml/credentials/Credentials.qml
104107
qml/credentials/OAuthCredentials.qml
105108

src/gui/accountmanager.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ void AccountManager::save()
157157
}
158158

159159
// save the account state
160-
this->account(account->uuid())->writeToSettings(settings);
160+
accountState->writeToSettings(settings);
161161
}
162162
settings.endArray();
163163

src/gui/accountsettings.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "libsync/graphapi/spacesmanager.h"
3939
#include "libsync/syncresult.h"
4040
#include "libsync/theme.h"
41+
#include "networkjobs/jsonjob.h"
4142
#include "scheduling/syncscheduler.h"
4243
#include "settingsdialog.h"
4344

@@ -46,6 +47,8 @@
4647
#include <QSortFilterProxyModel>
4748
#include <QtQuickWidgets/QtQuickWidgets>
4849

50+
using namespace std::chrono_literals;
51+
4952
namespace {
5053
constexpr auto modalWidgetStretchedMarginC = 50;
5154
}
@@ -91,6 +94,11 @@ AccountSettings::AccountSettings(const AccountStatePtr &accountState, QWidget *p
9194
}
9295
});
9396
ui->stackedWidget->setCurrentWidget(ui->quickWidget);
97+
98+
auto *notificationsPollTimer = new QTimer(this);
99+
notificationsPollTimer->setInterval(1min);
100+
notificationsPollTimer->start();
101+
connect(notificationsPollTimer, &QTimer::timeout, this, &AccountSettings::updateNotifications);
94102
}
95103

96104
void AccountSettings::slotToggleSignInState()
@@ -102,6 +110,20 @@ void AccountSettings::slotToggleSignInState()
102110
}
103111
}
104112

113+
void AccountSettings::markNotificationsRead()
114+
{
115+
if (!_notifications.isEmpty()) {
116+
auto *job = Notification::dismissAllNotifications(_accountState->account(), _notifications, this);
117+
connect(job, &JsonApiJob::finishedSignal, this, [job, this] {
118+
if (job->httpStatusCode() == 200) {
119+
_notifications.clear();
120+
Q_EMIT notificationsChanged();
121+
}
122+
});
123+
job->start();
124+
}
125+
}
126+
105127
void AccountSettings::showSelectiveSyncDialog(Folder *folder)
106128
{
107129
auto *selectiveSync = new SelectiveSyncWidget(_accountState->account(), this);
@@ -318,6 +340,17 @@ void AccountSettings::doForceSyncCurrentFolder(Folder *selectedFolder)
318340
// Restart scheduler
319341
FolderMan::instance()->scheduler()->start();
320342
}
343+
void AccountSettings::updateNotifications()
344+
{
345+
if (_accountState->isConnected()) {
346+
auto *job = Notification::createNotificationsJob(_accountState->account(), this);
347+
connect(job, &JsonApiJob::finishedSignal, this, [job, this] {
348+
_notifications = Notification::getNotifications(job);
349+
Q_EMIT notificationsChanged();
350+
});
351+
job->start();
352+
}
353+
}
321354

322355
void AccountSettings::slotAccountStateChanged()
323356
{
@@ -342,6 +375,7 @@ void AccountSettings::slotAccountStateChanged()
342375
connect(
343376
accountsState()->account()->spacesManager(), &GraphApi::SpacesManager::updated, this, &AccountSettings::slotSpacesUpdated, Qt::UniqueConnection);
344377
slotSpacesUpdated();
378+
updateNotifications();
345379
break;
346380
}
347381
case AccountState::ServiceUnavailable:
@@ -487,6 +521,11 @@ QString AccountSettings::accountStateIconName()
487521
return _accountStateIconName;
488522
}
489523

524+
const QList<Notification> &AccountSettings::notifications() const
525+
{
526+
return _notifications;
527+
}
528+
490529
void AccountSettings::slotDeleteAccount()
491530
{
492531
// Deleting the account potentially deletes 'this', so

src/gui/accountsettings.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "gui/opencloudguilib.h"
1818

1919
#include "folder.h"
20+
#include "gui/notifications.h"
2021
#include "gui/qmlutils.h"
2122
#include "owncloudgui.h"
2223
#include "progressdispatcher.h"
@@ -55,6 +56,7 @@ class OPENCLOUD_GUI_EXPORT AccountSettings : public QWidget
5556
Q_PROPERTY(uint syncedSpaces READ syncedSpaces NOTIFY syncedSpacesChanged)
5657
Q_PROPERTY(QString connectionLabel READ connectionLabel NOTIFY connectionLabelChanged)
5758
Q_PROPERTY(QString accountStateIconName READ accountStateIconName NOTIFY connectionLabelChanged)
59+
Q_PROPERTY(QList<Notification> notifications READ notifications NOTIFY notificationsChanged)
5860
OC_DECLARE_WIDGET_FOCUS
5961
QML_ELEMENT
6062
QML_UNCREATABLE("C++ only")
@@ -79,11 +81,14 @@ class OPENCLOUD_GUI_EXPORT AccountSettings : public QWidget
7981
QString connectionLabel();
8082
QString accountStateIconName();
8183

84+
const QList<Notification> &notifications() const;
85+
8286
Q_SIGNALS:
8387
void showIssuesList();
8488
void unsyncedSpacesChanged();
8589
void syncedSpacesChanged();
8690
void connectionLabelChanged();
91+
void notificationsChanged();
8792

8893
public Q_SLOTS:
8994
void slotAccountStateChanged();
@@ -100,12 +105,15 @@ protected Q_SLOTS:
100105
void slotFolderWizardAccepted();
101106
void slotDeleteAccount();
102107
void slotToggleSignInState();
108+
void markNotificationsRead();
103109

104110
private:
105111
void showConnectionLabel(const QString &message, SyncResult::Status status, QStringList errors = {});
106112

107113
void doForceSyncCurrentFolder(Folder *selectedFolder);
108114

115+
void updateNotifications();
116+
109117
Ui::AccountSettings *ui;
110118

111119
FolderStatusModel *_model;
@@ -118,6 +126,8 @@ protected Q_SLOTS:
118126
uint _unsyncedSpaces = 0;
119127
QString _connectionLabel;
120128
QString _accountStateIconName;
129+
130+
QList<Notification> _notifications;
121131
};
122132

123133
} // namespace OCC

src/gui/notifications.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
// SPDX-FileCopyrightText: 2025 Hannah von Reth <h.vonreth@opencloud.eu>
3+
4+
#include "notifications.h"
5+
6+
#include "libsync/account.h"
7+
#include "networkjobs/jsonjob.h"
8+
9+
using namespace OCC;
10+
11+
Notification::Notification() { }
12+
13+
Notification::Notification(const QString &title, const QString &message, const QString &id)
14+
: title(title)
15+
, message(message)
16+
, id(id)
17+
{
18+
}
19+
20+
JsonApiJob *Notification::dismissAllNotifications(const AccountPtr &account, const QList<Notification> &notifications, QObject *parent)
21+
{
22+
QStringList ids;
23+
for (const Notification &n : notifications) {
24+
ids.append(n.id);
25+
}
26+
27+
return new JsonApiJob(account, account->url(), QStringLiteral("ocs/v2.php/apps/notifications/api/v1/notifications"), "DELETE",
28+
QJsonObject{{QStringLiteral("ids"), QJsonArray::fromStringList(ids)}}, {}, parent);
29+
}
30+
31+
JsonApiJob *Notification::createNotificationsJob(const AccountPtr &account, QObject *parent)
32+
{
33+
return new JsonApiJob(account, QStringLiteral("ocs/v2.php/apps/notifications/api/v1/notifications"), {}, {}, parent);
34+
}
35+
36+
QList<Notification> Notification::getNotifications(JsonApiJob *job)
37+
{
38+
if (job->ocsSuccess()) {
39+
const auto data = job->data().value(QLatin1String("ocs")).toObject().value(QLatin1String("data")).toArray();
40+
QList<Notification> notifications;
41+
notifications.reserve(data.size());
42+
for (const auto &notification : data) {
43+
const auto n = notification.toObject();
44+
notifications.emplace_back(n.value(QLatin1String("subject")).toString(), n.value(QLatin1String("message")).toString(),
45+
n.value(QLatin1String("notification_id")).toString());
46+
}
47+
return notifications;
48+
}
49+
return {};
50+
}

src/gui/notifications.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
// SPDX-FileCopyrightText: 2025 Hannah von Reth <h.vonreth@opencloud.eu>
3+
4+
#pragma once
5+
6+
#include "libsync/accountfwd.h"
7+
8+
#include <QQmlEngine>
9+
10+
namespace OCC {
11+
class JsonApiJob;
12+
13+
class Notification
14+
{
15+
Q_GADGET
16+
Q_PROPERTY(QString title MEMBER title CONSTANT)
17+
Q_PROPERTY(QString message MEMBER message CONSTANT)
18+
Q_PROPERTY(QString id MEMBER id CONSTANT)
19+
QML_VALUE_TYPE(notification)
20+
21+
public:
22+
Notification();
23+
Notification(const QString &title, const QString &message, const QString &id);
24+
25+
QString title;
26+
QString message;
27+
QString id;
28+
29+
static JsonApiJob *createNotificationsJob(const AccountPtr &account, QObject *parent);
30+
31+
static QList<Notification> getNotifications(JsonApiJob *job);
32+
33+
static JsonApiJob *dismissAllNotifications(const AccountPtr &account, const QList<Notification> &notifications, QObject *parent);
34+
};
35+
36+
}

src/gui/qml/FolderDelegate.qml

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Pane {
3535
target: widget
3636

3737
function onFocusFirst() {
38-
manageAccountButton.forceActiveFocus(Qt.TabFocusReason);
38+
notificationButton.forceActiveFocus(Qt.TabFocusReason);
3939
}
4040

4141
function onFocusLast() {
@@ -66,6 +66,43 @@ Pane {
6666
}
6767
// spacer
6868
Item {}
69+
Button {
70+
id: notificationButton
71+
spacing: folderSyncPanel.spacing
72+
73+
Popup {
74+
id: notificationPopup
75+
width: 300
76+
height: 200
77+
// kepp some distance to the window corner
78+
rightMargin: 10
79+
80+
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
81+
82+
contentItem: Frame {
83+
anchors.fill: parent
84+
Notifications {
85+
anchors.fill: parent
86+
notifications: accountSettings.notifications
87+
88+
onMarkReadClicked: accountSettings.markNotificationsRead()
89+
}
90+
}
91+
}
92+
93+
text: accountSettings.notifications.length
94+
icon.source: QMLResources.resourcePath("core", "bell", enabled)
95+
icon.color: "transparent"
96+
icon.height: 16
97+
icon.width: 16
98+
99+
onClicked: notificationPopup.open()
100+
101+
Keys.onBacktabPressed: {
102+
widget.parentFocusWidget.focusPrevious();
103+
}
104+
}
105+
69106
Button {
70107
id: manageAccountButton
71108
text: qsTr("Manage Account")
@@ -97,10 +134,6 @@ Pane {
97134
accountMenu.open();
98135
Accessible.announce(qsTr("Account options Menu"));
99136
}
100-
101-
Keys.onBacktabPressed: {
102-
widget.parentFocusWidget.focusPrevious();
103-
}
104137
}
105138
}
106139

0 commit comments

Comments
 (0)