Merges errors and protocols into notifications/activities.

- Errors will always be on top, then Notifications comes next and then Activities for last.
- Adds 2 new types of Activity: NotificationType and ErrorType.
- ActivityListModel and ActivityWidget stores AccountState to connect user and widget.

Signed-off-by: Camila San <hello@camila.codes>
This commit is contained in:
Camila San 2018-05-14 20:21:30 +02:00 committed by Roeland Jago Douma
parent d0c72dd642
commit a9cd3b3a6d
No known key found for this signature in database
GPG key ID: F941078878347C0C
9 changed files with 188 additions and 26 deletions

View file

@ -24,6 +24,8 @@
<file>resources/delete.png</file> <file>resources/delete.png</file>
<file>resources/close.svg</file> <file>resources/close.svg</file>
<file>resources/bell.svg</file> <file>resources/bell.svg</file>
<file>resources/link.svg</file>
<file>resources/files.svg</file>
</qresource> </qresource>
<qresource prefix="/"/> <qresource prefix="/"/>
</RCC> </RCC>

1
resources/files.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1"><path d="M1.457 1.997c-.25 0-.46.21-.46.46v11.08c0 .26.202.46.46.46h13.08c.258 0 .46-.2.46-.46V4.46c0-.25-.21-.463-.46-.463h-6.54l-2-2z" /></svg>

After

Width:  |  Height:  |  Size: 220 B

1
resources/link.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16"><path d="M7.4 0C3.3 0 0 3.3 0 7.4s3.3 7.4 7.4 7.4 7.4-3.3 7.4-7.4S11.5 0 7.4 0zm.8.9c1.3 0 2.4.8 3.5 1.3l1.8 2.5-.3 1.1.6.3V8.5c-.2.7-.6 1.3-.9 2-.2.1 0-.8-.1-1 0-.6-.5-.6-.9-.2-.4.3-1.4.3-1.5-.4-.3-.8 0-1.7.3-2.5l-.6-.7.2-1.8-.8-.9.2-1-1-.6c-.2-.2-.6-.2-.7-.4.1 0 .2-.1.2-.1zM5.6 1s.1 0 .1.1c.4.2-.1.4-.2.6-.5.3.3.7.5 1 .4-.1.8-.7 1.4-.5.7-.2.6.6 1.1 1 .1.2.9.8.4.6-.5-.4-1-.4-1.3.1-.8.5-.3-.9-.7-1.2-.6-.7-.4.5-.4.9-.4 0-1.1-.3-1.5.2l.4.6.5-.7c0-.3.1.2.3.3.1.2.8.7.3.9-.8.4-1.4 1.1-2.1 1.7-.2.5-.7.4-1 0-.7-.4-.7.7-.6 1.1l.6-.4v1.1c-.4.4-.9-.7-1.3-.9V5.9c0-.4-.1-.9 0-1.3.8-.9 1.7-1.9 2.2-3h.8c.6.2.3-.7.5-.6zM4.4 9.2c.1 0 .2 0 .3.1.8.1 1.4.7 2 1.1.5.5 1.6.3 1.7 1.2-.2.9-1.1 1.4-1.8 1.7-.2.1-.4.2-.6.2-.7.2-1-.6-1.2-1.1-.3-.7-1.1-1.2-1-2.1 0-.4.2-1 .6-1.1z"/></svg>

After

Width:  |  Height:  |  Size: 851 B

View file

@ -49,7 +49,8 @@ public:
enum Type { enum Type {
ActivityType, ActivityType,
NotificationType NotificationType,
ErrorType
}; };
Type _type; Type _type;
@ -60,6 +61,7 @@ public:
QUrl _link; QUrl _link;
QDateTime _dateTime; QDateTime _dateTime;
QString _accName; QString _accName;
int _status;
QVector<ActivityLink> _links; QVector<ActivityLink> _links;
/** /**

View file

@ -159,6 +159,43 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
primaryButton.features |= QStyleOptionButton::DefaultButton; primaryButton.features |= QStyleOptionButton::DefaultButton;
primaryButton.state |= QStyle::State_Raised; primaryButton.state |= QStyle::State_Raised;
// save info to be able to filter mouse clicks
_buttonHeight = buttonSize;
_spaceBetweenButtons = leftMargin;
_primaryButtonWidth = primaryButton.rect.size().width();
_secondaryButtonWidth = secondaryButton.rect.size().width();
} else if(activityType == Activity::Type::ErrorType){
int rightMargin = margin;
int leftMargin = margin * offset;
int top = option.rect.top() + margin - offset;
int buttonSize = option.rect.height()/2.5;
// Secondary will be 'Dismiss' or '...'
secondaryButton.rect = option.rect;
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/files.svg"));
int right = option.rect.right() - rightMargin;
int left = right - buttonSize;
secondaryButton.iconSize = QSize(buttonSize, buttonSize);
secondaryButton.rect.setLeft(left);
secondaryButton.rect.setRight(right);
secondaryButton.rect.setTop(top);
secondaryButton.rect.setHeight(_buttonHeight);
secondaryButton.features |= QStyleOptionButton::DefaultButton;
secondaryButton.state |= QStyle::State_Raised;
// Primary button will be 'More Information'
primaryButton.rect = option.rect;
primaryButton.text = tr("Open Browser");
right = secondaryButton.rect.left() - rightMargin;
left = secondaryButton.rect.left() - leftMargin;
primaryButton.rect.setLeft(left - fm.width(primaryButton.text));
primaryButton.rect.setRight(right);
primaryButton.rect.setTop(top);
primaryButton.rect.setHeight(_buttonHeight);
primaryButton.features |= QStyleOptionButton::DefaultButton;
primaryButton.state |= QStyle::State_Raised;
// save info to be able to filter mouse clicks // save info to be able to filter mouse clicks
_buttonHeight = buttonSize; _buttonHeight = buttonSize;
_spaceBetweenButtons = leftMargin; _spaceBetweenButtons = leftMargin;
@ -200,7 +237,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
painter->drawText(actionTextBox, elidedAction); painter->drawText(actionTextBox, elidedAction);
// draw the buttons // draw the buttons
if(activityType == Activity::Type::NotificationType){ if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::ErrorType){
QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter); QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter);
QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter); QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter);
} }
@ -235,7 +272,8 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index) const QStyleOptionViewItem &option, const QModelIndex &index)
{ {
if(qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::NotificationType){ if(qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::NotificationType
|| qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::ErrorType){
if (event->type() == QEvent::MouseButtonRelease){ if (event->type() == QEvent::MouseButtonRelease){
QMouseEvent *mouseEvent = (QMouseEvent*)event; QMouseEvent *mouseEvent = (QMouseEvent*)event;
if(mouseEvent){ if(mouseEvent){

View file

@ -29,6 +29,8 @@
#include "activitydata.h" #include "activitydata.h"
#include "activitylistmodel.h" #include "activitylistmodel.h"
#include "theme.h"
#include "servernotificationhandler.h" #include "servernotificationhandler.h"
namespace OCC { namespace OCC {
@ -83,15 +85,24 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return customList; return customList;
break; break;
} }
// case ActivityItemDelegate::UserIconRole:
// return QIcon(QLatin1String(":/client/resources/account.png"));
// break;
case ActivityItemDelegate::ActionIconRole: case ActivityItemDelegate::ActionIconRole:
if(a._type == Activity::NotificationType){ if(a._type == Activity::NotificationType){
QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id);
if(!cachedIcon.isNull()) if(!cachedIcon.isNull())
return cachedIcon; return cachedIcon;
else return QIcon(QLatin1String(":/client/resources/bell.svg")); else return QIcon(QLatin1String(":/client/resources/bell.svg"));
} else if(a._type == Activity::ErrorType){
if(a._status == SyncFileItem::NormalError
|| a._status == SyncFileItem::FatalError
|| a._status == SyncFileItem::DetailError
|| a._status == SyncFileItem::BlacklistedError) {
return Theme::instance()->syncStateIcon(SyncResult::Error);
} else if(a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::FileIgnored
|| a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration){
return Theme::instance()->syncStateIcon(SyncResult::Problem);
}
} else return QIcon(QLatin1String(":/client/resources/activity.png")); } else return QIcon(QLatin1String(":/client/resources/activity.png"));
return QVariant(); return QVariant();
break; break;
@ -204,7 +215,12 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
combineActivityLists(); combineActivityLists();
} }
void ActivityListModel::addToActivityList(Activity activity) { void ActivityListModel::addErrorToActivityList(Activity activity) {
_notificationErrorsLists.prepend(activity);
combineActivityLists();
}
void ActivityListModel::addNotificationToActivityList(Activity activity) {
_notificationLists.prepend(activity); _notificationLists.prepend(activity);
combineActivityLists(); combineActivityLists();
} }
@ -219,6 +235,9 @@ void ActivityListModel::combineActivityLists()
{ {
ActivityList resultList; ActivityList resultList;
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
resultList.append(_notificationErrorsLists);
std::sort(_notificationLists.begin(), _notificationLists.end()); std::sort(_notificationLists.begin(), _notificationLists.end());
resultList.append(_notificationLists); resultList.append(_notificationLists);

View file

@ -47,7 +47,8 @@ public:
void fetchMore(const QModelIndex &) Q_DECL_OVERRIDE; void fetchMore(const QModelIndex &) Q_DECL_OVERRIDE;
ActivityList activityList() { return _finalList; } ActivityList activityList() { return _finalList; }
void addToActivityList(Activity activity); void addNotificationToActivityList(Activity activity);
void addErrorToActivityList(Activity activity);
void removeFromActivityList(int row); void removeFromActivityList(int row);
public slots: public slots:
@ -66,6 +67,7 @@ private:
ActivityList _activityLists; ActivityList _activityLists;
ActivityList _notificationLists; ActivityList _notificationLists;
ActivityList _notificationErrorsLists;
ActivityList _finalList; ActivityList _finalList;
AccountState *_accountState; AccountState *_accountState;
bool _currentlyFetching = true; bool _currentlyFetching = true;

View file

@ -95,11 +95,17 @@ ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent)
connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView); connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView);
connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView); connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView);
//connect(this, &ActivityWidget::sendNotificationRequest, this, &ActivityWidget::slotSendNotificationRequest);
connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile); connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile);
connect(&_removeTimer, &QTimer::timeout, this, &ActivityWidget::slotCheckToCleanWidgets); connect(&_removeTimer, &QTimer::timeout, this, &ActivityWidget::slotCheckToCleanWidgets);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
this, &ActivityWidget::slotProgressInfo);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
this, &ActivityWidget::slotItemCompleted);
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
this, &ActivityWidget::addError);
_removeTimer.setInterval(1000); _removeTimer.setInterval(1000);
} }
@ -108,6 +114,75 @@ ActivityWidget::~ActivityWidget()
delete _ui; delete _ui;
} }
void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
{
if (progress.status() == ProgressInfo::Starting) {
// The sync is restarting, clean the old items
//cleanItems(folder);
}
}
void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){
auto folderInstance = FolderMan::instance()->folder(folder);
if (!folderInstance)
return;
// check if we are adding it to the right account and if it is useful information (error)
if(folderInstance->accountState() == _accountState){
Activity activity;
activity._type = Activity::ErrorType;
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
activity._subject = item->_errorString;
qDebug() << "TOTAL " << folder;
activity._message = item->_originalFile;
activity._link = folderInstance->remotePath();
activity._status = item->_status;
activity._accName = folderInstance->accountState()->account()->displayName();
activity._file = item->_file;
ActivityLink al;
al._label = tr("Open Folder");
al._link = folderInstance->path();
al._verb = "";
al._isPrimary = true;
activity._links.append(al);
_model->addErrorToActivityList(activity);
// add error widget
}
}
void ActivityWidget::addError(const QString &folderAlias, const QString &message,
ErrorCategory category)
{
auto folderInstance = FolderMan::instance()->folder(folderAlias);
if (!folderInstance)
return;
if(folderInstance->accountState() == _accountState){
Activity activity;
activity._type = Activity::ErrorType;
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
activity._subject = message;
activity._message = folderInstance->shortGuiLocalPath();
activity._link = folderInstance->shortGuiLocalPath();
activity._status = SyncResult::Error;
activity._accName = folderInstance->accountState()->account()->displayName();
if (category == ErrorCategory::InsufficientRemoteStorage) {
ActivityLink link;
link._label = tr("Retry all uploads");
link._link = folderInstance->path();
link._verb = "";
link._isPrimary = true;
activity._links.append(link);
}
_model->addErrorToActivityList(activity);
}
}
void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){ void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){
QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole)); QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole));
if(!link.isEmpty()) if(!link.isEmpty())
@ -121,6 +196,7 @@ void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &ind
actionLinks << qvariant_cast<ActivityLink>(customItem); actionLinks << qvariant_cast<ActivityLink>(customItem);
} }
if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){
const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString();
if(actionLinks.size() == 1){ if(actionLinks.size() == 1){
if(actionLinks.at(0)._verb == "DELETE") if(actionLinks.at(0)._verb == "DELETE")
@ -138,6 +214,17 @@ void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &ind
} }
} }
if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::ErrorType){
QString fileName = index.data(ActivityItemDelegate::PathRole).toString();
if (Folder *folder = FolderMan::instance()->folderForPath(actionLinks.first()._link)) {
QString fullPath = folder->path() + fileName;
if (QFile(fullPath).exists()) {
showInFileManager(fullPath);
}
}
}
}
void ActivityWidget::slotNotificationRequestFinished(int statusCode) void ActivityWidget::slotNotificationRequestFinished(int statusCode)
{ {
int row = sender()->property("activityRow").toInt(); int row = sender()->property("activityRow").toInt();
@ -369,7 +456,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
} }
} }
_model->addToActivityList(activity); _model->addNotificationToActivityList(activity);
} }
} }
@ -566,11 +653,13 @@ ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog); connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog);
connect(_activityWidget, &ActivityWidget::newNotification, this, &ActivitySettings::slotShowActivityTab); connect(_activityWidget, &ActivityWidget::newNotification, this, &ActivitySettings::slotShowActivityTab);
// _protocolWidget = new ProtocolWidget(this); _protocolWidget = new ProtocolWidget(this);
_vbox->addWidget(_protocolWidget);
// _protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); // _protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
// connect(_protocolWidget, &ProtocolWidget::copyToClipboard, this, &ActivitySettings::slotCopyToClipboard); // connect(_protocolWidget, &ProtocolWidget::copyToClipboard, this, &ActivitySettings::slotCopyToClipboard);
// _issuesWidget = new IssuesWidget(this); _issuesWidget = new IssuesWidget(this);
_vbox->addWidget(_issuesWidget);
// _syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString()); // _syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString());
// slotShowIssueItemCount(0); // to display the label. // slotShowIssueItemCount(0); // to display the label.
// connect(_issuesWidget, &IssuesWidget::issueCountUpdated, // connect(_issuesWidget, &IssuesWidget::issueCountUpdated,

View file

@ -79,6 +79,12 @@ public slots:
void slotRemoveAccount(); void slotRemoveAccount();
void slotAccountActivityStatus(int statusCode); void slotAccountActivityStatus(int statusCode);
void slotRequestCleanupAndBlacklist(const Activity &blacklistActivity); void slotRequestCleanupAndBlacklist(const Activity &blacklistActivity);
//
void addError(const QString &folderAlias, const QString &message, ErrorCategory category);
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
//void slotOpenFile(QTreeWidgetItem *item, int);
signals: signals:
void guiLog(const QString &, const QString &); void guiLog(const QString &, const QString &);
@ -164,6 +170,8 @@ private:
bool event(QEvent *e) Q_DECL_OVERRIDE; bool event(QEvent *e) Q_DECL_OVERRIDE;
ActivityWidget *_activityWidget; ActivityWidget *_activityWidget;
ProtocolWidget *_protocolWidget;
IssuesWidget *_issuesWidget;
QProgressIndicator *_progressIndicator; QProgressIndicator *_progressIndicator;
QTimer _notificationCheckTimer; QTimer _notificationCheckTimer;
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck; QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;