Skip to content

Commit

Permalink
Display server notifications on the client (#3733)
Browse files Browse the repository at this point in the history
As interaction is required, the notifications are displayed in a
separate widget above the server activity list.

Note that design and also where we display the notifications can
still be discussed and changed.
  • Loading branch information
dragotin committed Mar 10, 2016
1 parent 688c550 commit 32e16b3
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ set(client_UI
owncloudsetuppage.ui
addcertificatedialog.ui
proxyauthdialog.ui
notificationwidget.ui
wizard/owncloudadvancedsetuppage.ui
wizard/owncloudconnectionmethoddialog.ui
wizard/owncloudhttpcredspage.ui
Expand Down Expand Up @@ -84,6 +85,8 @@ set(client_SRCS
proxyauthhandler.cpp
proxyauthdialog.cpp
synclogdialog.cpp
notificationwidget.cpp
notificationconfirmjob.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.cpp
creds/shibbolethcredentials.cpp
Expand Down
164 changes: 164 additions & 0 deletions src/gui/activitywidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
#include "activityitemdelegate.h"
#include "protocolwidget.h"
#include "QProgressIndicator.h"
#include "notificationwidget.h"
#include "notificationconfirmjob.h"

#include "ui_activitywidget.h"

Expand All @@ -52,6 +54,20 @@ QString ActivityList::accountName() const

/* ==================================================================== */

QHash <QString, QVariant> ActivityLink::toVariantHash()
{
QHash<QString, QVariant> hash;

hash["label"] = _label;
hash["link"] = _link;
hash["verb"] = _verb;
hash["primary"] = _isPrimary;

return hash;
}

/* ==================================================================== */

ActivityListModel::ActivityListModel(QWidget *parent)
:QAbstractListModel(parent)
{
Expand Down Expand Up @@ -176,6 +192,7 @@ void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int stat
auto json = activ.toMap();

Activity a;
a._type = Activity::ActivityType;
a._accName = ast->account()->displayName();
a._id = json.value("id").toLongLong();
a._subject = json.value("subject").toString();
Expand Down Expand Up @@ -277,6 +294,16 @@ ActivityWidget::ActivityWidget(QWidget *parent) :
_ui->_activityList->setAlternatingRowColors(true);
_ui->_activityList->setModel(_model);

_ui->_notifyLabel->hide();
_ui->_notifyScroll->hide();

// Create a widget container for the notifications. The ui file defines
// a scroll area that get a widget with a layout as children
QWidget *w = new QWidget(this);
_notificationsLayout = new QVBoxLayout(this);
w->setLayout(_notificationsLayout);
_ui->_notifyScroll->setWidget(w);

showLabels();

connect(_model, SIGNAL(activityJobStatusCode(AccountState*,int)),
Expand All @@ -290,6 +317,9 @@ ActivityWidget::ActivityWidget(QWidget *parent) :

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

connect( this, SIGNAL(newNotificationList(ActivityList)), this,
SLOT(slotBuildNotificationDisplay(ActivityList)) );
}

ActivityWidget::~ActivityWidget()
Expand All @@ -300,6 +330,7 @@ ActivityWidget::~ActivityWidget()
void ActivityWidget::slotRefresh(AccountState *ptr)
{
_model->slotRefreshActivity(ptr);
slotFetchNotifications(ptr);
}

void ActivityWidget::slotRemoveAccount( AccountState *ptr )
Expand All @@ -313,6 +344,8 @@ void ActivityWidget::showLabels()
_ui->_headerLabel->setTextFormat(Qt::RichText);
_ui->_headerLabel->setText(t);

_ui->_notifyLabel->setText(tr("Action Required: Notifications"));

t.clear();
QSetIterator<QString> i(_accountsWithoutActivities);
while (i.hasNext() ) {
Expand Down Expand Up @@ -400,6 +433,137 @@ void ActivityWidget::slotOpenFile(QModelIndex indx)
}
}

void ActivityWidget::slotFetchNotifications(AccountState *ptr)
{
/* start the notification fetch job as well */
if( !ptr) {
return;
}

// if the previous notification job has finished, start next.
if( !_notificationJob ) {
_notificationJob = new JsonApiJob( ptr->account(), QLatin1String("ocs/v2.php/apps/notifications/api/v1/notifications"), this );
QObject::connect(_notificationJob.data(), SIGNAL(jsonReceived(QVariantMap, int)),
this, SLOT(slotNotificationsReceived(QVariantMap, int)));
_notificationJob->setProperty("AccountStatePtr", QVariant::fromValue<AccountState*>(ptr));

qDebug() << "Start fetching notifications for " << ptr->account()->displayName();
_notificationJob->start();
} else {
qDebug() << "Notification Job still running, not starting a new one.";
}
}


void ActivityWidget::slotNotificationsReceived(const QVariantMap& json, int statusCode)
{
if( statusCode != 200 ) {
qDebug() << "Failed for Notifications";
return;
}

auto notifies = json.value("ocs").toMap().value("data").toList();

AccountState* ai = qvariant_cast<AccountState*>(sender()->property("AccountStatePtr"));

qDebug() << "Notifications for " << ai->account()->displayName() << notifies;

ActivityList list;

foreach( auto element, notifies ) {
Activity a;
auto json = element.toMap();
a._type = Activity::NotificationType;
a._accName = ai->account()->displayName();
a._id = json.value("notification_id").toLongLong();
a._subject = json.value("subject").toString();
a._message = json.value("message").toString();
QString s = json.value("link").toString();
if( !s.isEmpty() ) {
a._link = QUrl(s);
}
a._dateTime = json.value("datetime").toDateTime();
a._dateTime.setTimeSpec(Qt::UTC);

auto actions = json.value("actions").toList();
foreach( auto action, actions) {
auto actionJson = action.toMap();
ActivityLink al;
al._label = QUrl::fromPercentEncoding(actionJson.value("label").toByteArray());
al._link = actionJson.value("link").toString();
al._verb = actionJson.value("type").toString();
al._isPrimary = actionJson.value("primary").toBool();

a._links.append(al);
}

list.append(a);
}
emit newNotificationList( list );
}

// GUI: Display the notifications
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList& list)
{
foreach( auto activity, list ) {
NotificationWidget *widget = 0;

if( _widgetForNotifId.contains(activity._id) ) {
widget = _widgetForNotifId[activity._id];
} else {
widget = new NotificationWidget(this);
connect(widget, SIGNAL(sendNotificationRequest(QString, QString, QString)),
this, SLOT(slotSendNotificationRequest(QString, QString, QString)));
_notificationsLayout->addWidget(widget);
// _ui->_notifyScroll->setMinimumHeight( widget->height());
_ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
_widgetForNotifId[activity._id] = widget;
}

widget->setAccountName( activity._accName );
widget->setActivity( activity );
}
_ui->_notifyLabel->setHidden( list.count() == 0 );
_ui->_notifyScroll->setHidden( list.count() == 0 );
}

void ActivityWidget::slotSendNotificationRequest(const QString& accountName, const QString& link, const QString& verb)
{
qDebug() << "Server Notification Request " << verb << link << "on account" << accountName;

const QStringList validVerbs = QStringList() << "GET" << "PUT" << "POST" << "DELETE";

if( validVerbs.contains(verb)) {
AccountStatePtr acc = AccountManager::instance()->account(accountName);
if( acc ) {
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
QString myLink(link);
QUrl l(myLink);
job->setLinkAndVerb(l, verb);
connect( job, SIGNAL( networkError(QNetworkReply*)),
this, SLOT(slotNotifyNetworkError(QNetworkReply*)));
connect( job, SIGNAL( jobFinished(QString, int)),
this, SLOT(slotNotifyServerFinished(QString, int)) );
job->start();
}
} else {
qDebug() << "Invalid verb:" << verb;
}
}


void ActivityWidget::slotNotifyNetworkError( QNetworkReply* )
{
qDebug() << "Server notify job failed.";
}

void ActivityWidget::slotNotifyServerFinished( const QString& reply, int replyCode )
{
// FIXME: remove the widget after a couple of seconds
qDebug() << "Server Notification reply code"<< replyCode << reply;

}

/* ==================================================================== */

ActivitySettings::ActivitySettings(QWidget *parent)
Expand Down
38 changes: 37 additions & 1 deletion src/gui/activitywidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,31 @@ namespace OCC {
class Account;
class AccountStatusPtr;
class ProtocolWidget;
class JsonApiJob;
class NotificationWidget;

namespace Ui {
class ActivityWidget;
}
class Application;

/**
* @brief The ActivityLink class describes actions of an activity
*
* These are part of notifications which are mapped into activities.
*/

class ActivityLink
{
public:
QHash <QString, QVariant> toVariantHash();

QString _label;
QString _link;
QString _verb;
bool _isPrimary;
};

/**
* @brief Activity Structure
* @ingroup gui
Expand All @@ -49,6 +68,11 @@ class Application;
class Activity
{
public:
enum Type {
ActivityType,
NotificationType
};
Type _type;
qlonglong _id;
QString _subject;
QString _message;
Expand All @@ -57,6 +81,7 @@ class Activity
QDateTime _dateTime;
QString _accName;

QVector <ActivityLink> _links;
/**
* @brief Sort operator to sort the list youngest first.
* @param val
Expand Down Expand Up @@ -146,12 +171,21 @@ public slots:
void slotRefresh(AccountState* ptr);
void slotRemoveAccount( AccountState *ptr );
void slotAccountActivityStatus(AccountState *ast, int statusCode);
void slotFetchNotifications(AccountState *ptr);

signals:
void guiLog(const QString&, const QString&);
void copyToClipboard();
void rowsInserted();
void hideAcitivityTab(bool);
void newNotificationList(const ActivityList& list);

private slots:
void slotNotificationsReceived(const QVariantMap& json, int statusCode);
void slotBuildNotificationDisplay(const ActivityList& list);
void slotSendNotificationRequest(const QString &accountName, const QString& link, const QString& verb);
void slotNotifyNetworkError( QNetworkReply* );
void slotNotifyServerFinished( const QString& reply, int replyCode );

private:
void showLabels();
Expand All @@ -160,8 +194,10 @@ public slots:
QPushButton *_copyBtn;

QSet<QString> _accountsWithoutActivities;

QMap<int, NotificationWidget*> _widgetForNotifId;
QPointer<JsonApiJob> _notificationJob;
ActivityListModel *_model;
QVBoxLayout *_notificationsLayout;
};


Expand Down
43 changes: 40 additions & 3 deletions src/gui/activitywidget.ui
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,60 @@
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="_headerLabel">
<widget class="QLabel" name="_notifyLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QListView" name="_activityList"/>
<widget class="QScrollArea" name="_notifyScroll">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="_scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>677</width>
<height>70</height>
</rect>
</property>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="_bottomLabel">
<widget class="QLabel" name="_headerLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QListView" name="_activityList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="_bottomLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QDialogButtonBox" name="_dialogButtonBox"/>
</item>
</layout>
Expand Down

0 comments on commit 32e16b3

Please sign in to comment.