Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show Server Notifications on the Client #4567

Merged
merged 44 commits into from
Apr 4, 2016
Merged
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
0eb1041
AbstractNetworkJob: Add a delete job.
Mar 4, 2016
a831b74
Added temporar icon for notifications.
Mar 4, 2016
eb00b34
Minor wording fixes
Mar 4, 2016
688c550
New GUI class NotificationWidget.
Mar 4, 2016
32e16b3
Display server notifications on the client (#3733)
Mar 4, 2016
4a4dac2
Notifications: Add a Progress indicator and handle job results.
Mar 9, 2016
b97c832
Capabilities: Add isValid check and check for notifications
Mar 9, 2016
7d13a1d
Notifications: Check capabilities if the notifications are enabled
Mar 9, 2016
8a0ce46
Notifications: Properly delete the notification check job.
Mar 9, 2016
903e79a
Notifications: Do a GUI tray notification if new notifciations arrive.
Mar 10, 2016
2d1ab27
Notifications: Refactor - create a notification handler class
Mar 10, 2016
2c2a18a
Activitiy: Refactor - move classes to their own source files.
Mar 11, 2016
adf9570
Notification: Enhance the tray message
Mar 11, 2016
73cd5a9
Notifications: Cleaner notification string build
Mar 14, 2016
97f1694
ActivityData: Simplified implementation.
Mar 14, 2016
9d219a1
ActivityListModel: Code cleanups
Mar 14, 2016
9a2f145
ocs jobs: Add a define for OCS job success.
Mar 14, 2016
a4dcc27
Notification: Fix plural handling for tray message
Mar 14, 2016
45c32ec
NotificationWidget: Remove not needed method.
Mar 16, 2016
f7f4120
Activity: Some documentation and better varialbe names
Mar 16, 2016
f71fdab
Fix timeAgoInWords
Mar 18, 2016
05de710
Notifications: Display timestamp of the notification in the widget
Mar 18, 2016
0a590b7
Notifications: Give feedback if notifcation request succeeded.
Mar 18, 2016
328d254
Notifications: Remove "done" notification widgets after fife seconds.
Mar 18, 2016
7f22a07
Notifications: Check if the account is connected before querying.
Mar 11, 2016
b966345
Notifications: Refresh the notifications based on a config value.
Mar 18, 2016
f587f35
Fix plural translation handling, remove the superflous arg()
Mar 18, 2016
d407aac
Notifications: remove notification widgets if the notification is gone.
Mar 21, 2016
d03fcc9
Notifications: Maintain a timeSinceLastCheck for every Account.
Mar 22, 2016
ad60e8a
Notifications: Fix handling of notifications to remove from the list.
Mar 22, 2016
f70c628
Notifications: Remove unused variable.
Mar 22, 2016
ea2f19b
Docs: Add new config option for the notification sync interval.
Mar 22, 2016
161d219
ActivityData: Add source file for implementation details
Mar 23, 2016
1bb3a4a
NotificationWidget: Remove accountName() and add activity() method.
Mar 23, 2016
0c944a0
NotificationWidgetUI: Fix sizing and sizePolicy
Mar 23, 2016
1fe5d6b
Notifications: Handle Notifications without an action.
Mar 23, 2016
69e8e15
Remove explicit time spec specification as it is not needed.
Mar 29, 2016
4d59f5e
ActivityData: Declare operators outside the class
Mar 29, 2016
cacb751
Cleaups based on review feedback.
Mar 29, 2016
9f438cb
Doc: Add milliseconds unit to notificationRefreshInterval doc
Mar 29, 2016
2e30a0e
Remove superflous iterator increment
Mar 29, 2016
cd3f612
ActivityWidget: Rename blacklistActivities to blacklistNotifications.
Mar 29, 2016
8166c52
NotificationHandling: Use QByteArray for the verb.
Mar 29, 2016
885f8b3
ActivityWidget: Handle plural properly in translations.
Mar 29, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Notifications: Handle Notifications without an action.
The policy that was said is that if a notification has no action, the
client can and should display a close-button. This patch does it.

In additon to that, the client needs a blacklist of closed notifcations
otherwise they would re-appear next time the server notifications are
fetched again.

Also, changed the cleanup of not-longer-used widgets to be more robust.
  • Loading branch information
Klaas Freitag committed Mar 23, 2016
commit 1fe5d6bb0cfb6d556b8eb6bf7b35a668cdcff243
84 changes: 67 additions & 17 deletions src/gui/activitywidget.cpp
Original file line number Diff line number Diff line change
@@ -94,6 +94,9 @@ ActivityWidget::ActivityWidget(QWidget *parent) :

connect( _ui->_activityList, SIGNAL(activated(QModelIndex)), this,
SLOT(slotOpenFile(QModelIndex)));

connect( &_removeTimer, SIGNAL(timeout()), this, SLOT(slotCheckToCleanWidgets()) );
_removeTimer.setInterval(1000);
}

ActivityWidget::~ActivityWidget()
@@ -231,18 +234,26 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list)
QString listAccountName;

foreach( auto activity, list ) {
if( _blacklistedActivities.contains(activity)) {
qDebug() << Q_FUNC_INFO << "Activity in blacklist, skip";
continue;
}

NotificationWidget *widget = 0;

if( _widgetForNotifId.contains(activity._id) ) {
widget = _widgetForNotifId[activity._id];
if( _widgetForNotifId.contains( activity.ident()) ) {
widget = _widgetForNotifId[activity.ident()];
} else {
widget = new NotificationWidget(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When are these widgets deleted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot to add that to the TODO list in the first comment.

connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QString)),
this, SLOT(slotSendNotificationRequest(QString, QString, QString)));
connect(widget, SIGNAL(requestCleanupAndBlacklist(Activity)),
this, SLOT(slotRequestCleanupAndBlacklist(Activity)));

_notificationsLayout->addWidget(widget);
// _ui->_notifyScroll->setMinimumHeight( widget->height());
_ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
_widgetForNotifId[activity._id] = widget;
_widgetForNotifId[activity.ident()] = widget;
}

widget->setActivity( activity );
@@ -280,19 +291,20 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list)
}
}

// check if we have widgets that have no corresponding activity from
// check if there are widgets that have no corresponding activity from
// the server any more. Collect them in a list
QList<int> strayCats;
QList< Activity::Identifier > strayCats;
foreach( auto id, _widgetForNotifId.keys() ) {
bool found = false;
NotificationWidget *widget = _widgetForNotifId[id];

bool found = false;
// do not mark widgets of other accounts to delete.
if( widget->accountName() != listAccountName ) {
if( widget->activity()._accName != listAccountName ) {
continue;
}

foreach( auto activity, list ) {
if( activity._id == id ) {
if( activity.ident() == id ) {
// found an activity
found = true;
break;
@@ -307,8 +319,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list)
// .. and now delete all these stray cat widgets.
foreach( auto strayCatId, strayCats ) {
NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId];
widgetToGo->deleteLater();
_widgetForNotifId.remove(strayCatId);
scheduleWidgetToRemove(widgetToGo, 0);
}

_ui->_notifyLabel->setHidden( _widgetForNotifId.isEmpty() );
@@ -410,21 +421,60 @@ void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCo
// Add 200 millisecs to the predefined value to make sure that the timer in
// widget's method readyToClose() has elapsed.
if( replyCode == OCS_SUCCESS_STATUS_CODE ) {
QTimer::singleShot(NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS+200, this, SLOT(slotCleanWidgetList()));
scheduleWidgetToRemove( job->widget() );
}
}

// blacklist the activity coming in here.
void ActivityWidget::slotRequestCleanupAndBlacklist(const Activity& blacklistActivity)
{
if ( ! _blacklistedActivities.contains(blacklistActivity) ) {
_blacklistedActivities.append(blacklistActivity);
}

NotificationWidget *widget = _widgetForNotifId[ blacklistActivity.ident() ];
scheduleWidgetToRemove(widget);
}

void ActivityWidget::slotCleanWidgetList()
void ActivityWidget::scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds)
{
foreach( int id, _widgetForNotifId.keys() ) {
Q_ASSERT(_widgetForNotifId[id]);
if( _widgetForNotifId[id]->readyToClose() ) {
auto *widget = _widgetForNotifId[id];
if( !widget ) {
return;
}
// in fife seconds from now, remove the widget.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"five", but it's actually configurable...

QDateTime removeTime = QDateTime::currentDateTime().addMSecs(milliseconds);

QPair<QDateTime, NotificationWidget*> removeInfo = qMakePair(removeTime, widget);
if( !_widgetsToRemove.contains(removeInfo) ) {
_widgetsToRemove.insert( removeInfo );
if( !_removeTimer.isActive() ) {
_removeTimer.start();
}
}
}

// Called every second to see if widgets need to be removed.
void ActivityWidget::slotCheckToCleanWidgets()
{
// loop over all widgets in the to-remove queue
foreach( auto toRemove, _widgetsToRemove ) {
QDateTime t = toRemove.first;
NotificationWidget *widget = toRemove.second;

if( QDateTime::currentDateTime() > t ) {
// found one to remove!
Activity::Identifier id = widget->activity().ident();
_widgetForNotifId.remove(id);
delete widget;
widget->deleteLater();
_widgetsToRemove.remove(toRemove);
}
}

if( _widgetsToRemove.isEmpty() ) {
_removeTimer.stop();
}

// check to see if the whole notification pane should be hidden
if( _widgetForNotifId.isEmpty() ) {
_ui->_notifyLabel->setHidden(true);
_ui->_notifyScroll->setHidden(true);
10 changes: 8 additions & 2 deletions src/gui/activitywidget.h
Original file line number Diff line number Diff line change
@@ -66,6 +66,7 @@ public slots:
void slotRefreshNotifications(AccountState *ptr);
void slotRemoveAccount( AccountState *ptr );
void slotAccountActivityStatus(AccountState *ast, int statusCode);
void slotRequestCleanupAndBlacklist(const Activity& blacklistActivity);

signals:
void guiLog(const QString&, const QString&);
@@ -80,7 +81,8 @@ private slots:
void slotNotifyNetworkError( QNetworkReply* );
void slotNotifyServerFinished( const QString& reply, int replyCode );
void endNotificationRequest(NotificationWidget *widget , int replyCode);
void slotCleanWidgetList();
void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 4500);
void slotCheckToCleanWidgets();

private:
void showLabels();
@@ -89,9 +91,13 @@ private slots:
QPushButton *_copyBtn;

QSet<QString> _accountsWithoutActivities;
QMap<int, NotificationWidget*> _widgetForNotifId;
QMap<Activity::Identifier, NotificationWidget*> _widgetForNotifId;
QElapsedTimer _guiLogTimer;
QSet<int> _guiLoggedNotifications;
ActivityList _blacklistedActivities;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this needs to be a QSet otherwise your code might get very slow if there are many blacklisted activities (O(n²))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the "blacklisted activities" are actually these notifications that come without any action, which means that they do not display a button. These are only a few.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't persist this information - does it mean users have to acknowledge the server notification every time the client is restarted?

I assume that for notifications with actions, the server will keep track of them and that's how we avoid presenting them to the user multiple times...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we present the notifications from the server again, IF the notification is of the special form that has no action, ie. no button defined. I think that is the desired behaviour, as the admin will disable the notifification if it is not longer important.


QSet< QPair<QDateTime, NotificationWidget*> > _widgetsToRemove;
QTimer _removeTimer;

// number of currently running notification requests. If non zero,
// no query for notifications is started.
44 changes: 27 additions & 17 deletions src/gui/notificationwidget.cpp
Original file line number Diff line number Diff line change
@@ -54,13 +54,22 @@ void NotificationWidget::setActivity(const Activity& activity)
foreach( auto button, _ui._buttonBox->buttons() ) {
_ui._buttonBox->removeButton(button);
}
_buttons.clear();

// display buttons for the links
foreach( auto link, activity._links ) {
QPushButton *b = _ui._buttonBox->addButton( link._label, QDialogButtonBox::AcceptRole);
b->setDefault(link._isPrimary);
if( activity._links.isEmpty() ) {
// in case there is no action defined, do a close button.
QPushButton *b = _ui._buttonBox->addButton( QDialogButtonBox::Close );
b->setDefault(true);
connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
_buttons.append(b);
} else {
foreach( auto link, activity._links ) {
QPushButton *b = _ui._buttonBox->addButton(link._label, QDialogButtonBox::AcceptRole);
b->setDefault(link._isPrimary);
connect(b, SIGNAL(clicked()), this, SLOT(slotButtonClicked()));
_buttons.append(b);
}
}
}

@@ -84,12 +93,24 @@ void NotificationWidget::slotButtonClicked()
}

// if the button was found, the link must be called
if( index > -1 && _myActivity._links.count() == 0 ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: You could have a different slot for the close button.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not that easy because the available buttons per notification is not fixed, it is part of the notification description. It has to be dynamic.

// no links, that means it was the close button
// empty link. Just close and remove the widget.
QString doneText = tr("Closing in a few seconds...");
_ui._timeLabel->setText(doneText);
emit requestCleanupAndBlacklist(_myActivity);
return;
}

if( index > -1 && index < _myActivity._links.count() ) {
ActivityLink triggeredLink = _myActivity._links.at(index);
_actionLabel = triggeredLink._label;
qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link;
_progressIndi->startAnimation();
emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb );

if( ! triggeredLink._link.isEmpty() ) {
qDebug() << Q_FUNC_INFO << "Notification Link: "<< triggeredLink._verb << triggeredLink._link;
_progressIndi->startAnimation();
emit sendNotificationRequest( _accountName, triggeredLink._link, triggeredLink._verb );
}
}
}
}
@@ -116,22 +137,11 @@ void NotificationWidget::slotNotificationRequestFinished(int statusCode)

//* The second parameter is a time, such as 'selected at 09:58pm'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//:

doneText = tr("'%1' selected at %2").arg(_actionLabel).arg(timeStr);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here too


// start a timer, so that activity widget can remove this widget after a
// certain time. It needs to be done by ActivityWidget because it also
// needs to hide the scrollview if no widget is left any more.
// method readyToClose() checks for the timer value to see if it is expired
_closeTimer.start();
}
_ui._timeLabel->setText( doneText );

_progressIndi->stopAnimation();

}

bool NotificationWidget::readyToClose()
{
return _closeTimer.isValid() && _closeTimer.elapsed() > NOTIFICATION_WIDGET_CLOSE_AFTER_MILLISECS;
}

}
2 changes: 1 addition & 1 deletion src/gui/notificationwidget.h
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ class NotificationWidget : public QWidget

signals:
void sendNotificationRequest( const QString&, const QString& link, const QString& verb);
void requestCleanupAndBlacklist( const Activity& activity );

public slots:
void setActivity(const Activity& activity);
@@ -52,7 +53,6 @@ private slots:
QString _accountName;
QProgressIndicator *_progressIndi;
QString _actionLabel;
QElapsedTimer _closeTimer;
};

}