Skip to content

Commit

Permalink
Add a sortedactivitylistmodel that automatically handles sorting of a…
Browse files Browse the repository at this point in the history
…ctivities

Signed-off-by: Claudio Cambra <[email protected]>
  • Loading branch information
claucambra committed Sep 14, 2022
1 parent b91dad9 commit 6136b34
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 34 deletions.
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ set(client_SRCS
tray/usermodel.cpp
tray/notificationhandler.h
tray/notificationhandler.cpp
tray/sortedactivitylistmodel.h
tray/sortedactivitylistmodel.cpp
creds/credentialsfactory.h
tray/talkreply.cpp
creds/credentialsfactory.cpp
Expand Down
4 changes: 4 additions & 0 deletions src/gui/owncloudgui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "wheelhandler.h"
#include "common/syncjournalfilerecord.h"
#include "creds/abstractcredentials.h"
#include "tray/sortedactivitylistmodel.h"
#include "tray/syncstatussummary.h"
#include "tray/unifiedsearchresultslistmodel.h"

Expand Down Expand Up @@ -121,13 +122,16 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterType<UserStatusSelectorModel>("com.nextcloud.desktopclient", 1, 0, "UserStatusSelectorModel");
qmlRegisterType<ActivityListModel>("com.nextcloud.desktopclient", 1, 0, "ActivityListModel");
qmlRegisterType<FileActivityListModel>("com.nextcloud.desktopclient", 1, 0, "FileActivityListModel");
qmlRegisterType<SortedActivityListModel>("com.nextcloud.desktopclient", 1, 0, "SortedActivityListModel");
qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
qmlRegisterType<CallStateChecker>("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");

qmlRegisterUncreatableType<UnifiedSearchResultsListModel>("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
qmlRegisterUncreatableType<UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");

qRegisterMetaTypeStreamOperators<Emoji>();

qRegisterMetaType<ActivityListModel *>("ActivityListModel*");
qRegisterMetaType<UnifiedSearchResultsListModel *>("UnifiedSearchResultsListModel*");
qRegisterMetaType<UserStatus>("UserStatus");

Expand Down
4 changes: 2 additions & 2 deletions src/gui/tray/ActivityItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ ItemDelegate {

onShareButtonClicked: Systray.openShareDialog(model.displayPath, model.path)

onDismissButtonClicked: activityModel.slotTriggerDismiss(model.index)
onDismissButtonClicked: activityModel.slotTriggerDismiss(model.activityIndex)
}

Loader {
Expand All @@ -69,7 +69,7 @@ ItemDelegate {

sourceComponent: TalkReplyTextField {
onSendReply: {
UserModel.currentUser.sendReplyMessage(model.index, model.conversationToken, reply, model.messageId);
UserModel.currentUser.sendReplyMessage(model.activityIndex, model.conversationToken, reply, model.messageId);
talkReplyTextFieldLoader.visible = false;
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/gui/tray/ActivityList.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Style 1.0

ScrollView {
id: controlRoot
property alias model: activityList.model
property alias model: sortedActivityList.activityListModel

property bool isFileActivityList: false

Expand Down Expand Up @@ -48,6 +48,11 @@ ScrollView {
preferredHighlightBegin: 0
preferredHighlightEnd: controlRoot.height

model: NC.SortedActivityListModel {
id: sortedActivityList
activityListModel: controlRoot.model
}

delegate: ActivityItem {
isFileActivityList: controlRoot.isFileActivityList
width: activityList.contentWidth
Expand All @@ -73,7 +78,7 @@ ScrollView {
if (model.isCurrentUserFileActivity && model.openablePath) {
openFile("file://" + model.openablePath);
} else {
activityItemClicked(model.index)
activityItemClicked(model.activityIndex)
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions src/gui/tray/activitydata.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,14 @@ class Activity
public:
using Identifier = QPair<qlonglong, QString>;

// Note that these are in the order we want to present them in the model!
enum Type {
ActivityType,
DummyFetchingActivityType,
NotificationType,
SyncResultType,
SyncFileItemType
SyncFileItemType,
ActivityType,
DummyMoreActivitiesAvailableType,
};

static Activity fromActivityJson(const QJsonObject &json, const AccountPtr account);
Expand Down Expand Up @@ -144,7 +147,8 @@ class Activity
QVector<PreviewData> _previews;

// Stores information about the error
int _status;
SyncFileItem::Status _syncFileItemStatus;
SyncResult::Status _syncResultStatus;

QVector<ActivityLink> _links;
/**
Expand Down
39 changes: 24 additions & 15 deletions src/gui/tray/activitylistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[TalkNotificationMessageIdRole] = "messageId";
roles[TalkNotificationMessageSentRole] = "messageSent";
roles[TalkNotificationUserAvatarRole] = "userAvatar";
roles[ActivityIndexRole] = "activityIndex";
roles[ActivityRole] = "activity";

return roles;
Expand Down Expand Up @@ -220,20 +221,20 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
colorIconPath.append("state-error.svg");
return colorIconPath;
} else if (a._type == Activity::SyncFileItemType) {
if (a._status == SyncFileItem::NormalError
|| a._status == SyncFileItem::FatalError
|| a._status == SyncFileItem::DetailError
|| a._status == SyncFileItem::BlacklistedError) {
if (a._syncFileItemStatus == SyncFileItem::NormalError
|| a._syncFileItemStatus == SyncFileItem::FatalError
|| a._syncFileItemStatus == SyncFileItem::DetailError
|| a._syncFileItemStatus == SyncFileItem::BlacklistedError) {
colorIconPath.append("state-error.svg");
return colorIconPath;
} else if (a._status == SyncFileItem::SoftError
|| a._status == SyncFileItem::Conflict
|| a._status == SyncFileItem::Restoration
|| a._status == SyncFileItem::FileLocked
|| a._status == SyncFileItem::FileNameInvalid) {
} else if (a._syncFileItemStatus == SyncFileItem::SoftError
|| a._syncFileItemStatus == SyncFileItem::Conflict
|| a._syncFileItemStatus == SyncFileItem::Restoration
|| a._syncFileItemStatus == SyncFileItem::FileLocked
|| a._syncFileItemStatus == SyncFileItem::FileNameInvalid) {
colorIconPath.append("state-warning.svg");
return colorIconPath;
} else if (a._status == SyncFileItem::FileIgnored) {
} else if (a._syncFileItemStatus == SyncFileItem::FileIgnored) {
colorIconPath.append("state-info.svg");
return colorIconPath;
} else {
Expand Down Expand Up @@ -298,6 +299,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case ActionRole: {
switch (a._type) {
case Activity::ActivityType:
case Activity::DummyFetchingActivityType:
case Activity::DummyMoreActivitiesAvailableType:
return "Activity";
case Activity::NotificationType:
return "Notification";
Expand Down Expand Up @@ -336,7 +339,11 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case DisplayActions:
return _displayActions;
case ShareableRole:
return !data(index, PathRole).toString().isEmpty() && a._objectType == QStringLiteral("files") && _displayActions && a._fileAction != "file_deleted" && a._status != SyncFileItem::FileIgnored;
return !data(index, PathRole).toString().isEmpty() &&
a._objectType == QStringLiteral("files") &&
_displayActions &&
a._fileAction != "file_deleted" &&
a._syncFileItemStatus != SyncFileItem::FileIgnored;
case IsCurrentUserFileActivityRole:
return a._isCurrentUserFileActivity;
case ThumbnailRole: {
Expand All @@ -359,6 +366,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return replyMessageSent(a);
case TalkNotificationUserAvatarRole:
return a._talkNotificationData.userAvatar;
case ActivityIndexRole:
return index.row();
case ActivityRole:
return QVariant::fromValue(a);
}
Expand Down Expand Up @@ -465,7 +474,7 @@ void ActivityListModel::appendMoreActivitiesAvailableEntry()
&& _finalList.last()._objectType != moreActivitiesEntryObjectType) {

Activity a;
a._type = Activity::ActivityType;
a._type = Activity::DummyMoreActivitiesAvailableType;
a._accName = _accountState->account()->displayName();
a._id = -1;
a._objectType = moreActivitiesEntryObjectType;
Expand All @@ -485,7 +494,7 @@ void ActivityListModel::insertOrRemoveDummyFetchingActivity()
const QString dummyFetchingActivityObjectType = QLatin1String("dummy_fetching_activity");

if (_currentlyFetching && _finalList.isEmpty()) {
_dummyFetchingActivities._type = Activity::ActivityType;
_dummyFetchingActivities._type = Activity::DummyFetchingActivityType;
_dummyFetchingActivities._accName = _accountState->account()->displayName();
_dummyFetchingActivities._id = -2;
_dummyFetchingActivities._objectType = dummyFetchingActivityObjectType;
Expand Down Expand Up @@ -759,7 +768,7 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
const auto path = data(modelIndex, PathRole).toString();

const auto activity = _finalList.at(activityIndex);
if (activity._status == SyncFileItem::Conflict) {
if (activity._syncFileItemStatus == SyncFileItem::Conflict) {
Q_ASSERT(!activity._file.isEmpty());
Q_ASSERT(!activity._folder.isEmpty());
Q_ASSERT(Utility::isConflictFile(activity._file));
Expand Down Expand Up @@ -789,7 +798,7 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex)
_currentConflictDialog->open();
ownCloudGui::raiseDialog(_currentConflictDialog);
return;
} else if (activity._status == SyncFileItem::FileNameInvalid) {
} else if (activity._syncFileItemStatus == SyncFileItem::FileNameInvalid) {
if (!_currentInvalidFilenameDialog.isNull()) {
_currentInvalidFilenameDialog->close();
}
Expand Down
1 change: 1 addition & 0 deletions src/gui/tray/activitylistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class ActivityListModel : public QAbstractListModel
TalkNotificationMessageIdRole,
TalkNotificationMessageSentRole,
TalkNotificationUserAvatarRole,
ActivityIndexRole,
ActivityRole,
};
Q_ENUM(DataRole)
Expand Down
2 changes: 0 additions & 2 deletions src/gui/tray/notificationhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
}
}

a._status = 0;

QUrl link(json.value("link").toString());
if (!link.isEmpty()) {
if (link.host().isEmpty()) {
Expand Down
114 changes: 114 additions & 0 deletions src/gui/tray/sortedactivitylistmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (C) by Claudio Cambra <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

#include "activitylistmodel.h"

#include "sortedactivitylistmodel.h"

namespace OCC {

SortedActivityListModel::SortedActivityListModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}

void SortedActivityListModel::sortModel()
{
sort(0);
}

ActivityListModel* SortedActivityListModel::activityListModel() const
{
return static_cast<ActivityListModel*>(sourceModel());
}

void SortedActivityListModel::setActivityListModel(ActivityListModel* activityListModel)
{
const auto currentSetModel = sourceModel();

if(currentSetModel) {
disconnect(currentSetModel, &ActivityListModel::rowsInserted, this, &SortedActivityListModel::sortModel);
disconnect(currentSetModel, &ActivityListModel::rowsMoved, this, &SortedActivityListModel::sortModel);
disconnect(currentSetModel, &ActivityListModel::rowsRemoved, this, &SortedActivityListModel::sortModel);
disconnect(currentSetModel, &ActivityListModel::dataChanged, this, &SortedActivityListModel::sortModel);
disconnect(currentSetModel, &ActivityListModel::modelReset, this, &SortedActivityListModel::sortModel);
}

// Re-sort model when any changes take place
connect(activityListModel, &ActivityListModel::rowsInserted, this, &SortedActivityListModel::sortModel);
connect(activityListModel, &ActivityListModel::rowsMoved, this, &SortedActivityListModel::sortModel);
connect(activityListModel, &ActivityListModel::rowsRemoved, this, &SortedActivityListModel::sortModel);
connect(activityListModel, &ActivityListModel::dataChanged, this, &SortedActivityListModel::sortModel);
connect(activityListModel, &ActivityListModel::modelReset, this, &SortedActivityListModel::sortModel);

setSourceModel(activityListModel);
Q_EMIT activityListModelChanged();
}

bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
{
if (!sourceLeft.isValid() || !sourceRight.isValid()) {
return false;
}

const auto leftActivity = sourceLeft.data(ActivityListModel::ActivityRole).value<Activity>();
const auto rightActivity = sourceRight.data(ActivityListModel::ActivityRole).value<Activity>();

// First compare by general activity type
const auto leftType = leftActivity._type;

if(leftType == Activity::DummyFetchingActivityType) {
// The fetching activities dummy activity always goes at the top
return true;
} else if(leftType == Activity::DummyMoreActivitiesAvailableType) {
// Likewise the dummy "more activities available" activity always goes at the bottom
return false;
}

const auto rightType = rightActivity._type;

if(leftType != rightType) {
return leftType < rightType;
}

const auto leftSyncFileItemStatus = leftActivity._syncFileItemStatus;
const auto rightSyncFileItemStatus = rightActivity._syncFileItemStatus;

// Then compare by status
if(leftSyncFileItemStatus != rightSyncFileItemStatus) {
// We want to shove erors towards the top.
return (leftSyncFileItemStatus != SyncFileItem::NoStatus &&
leftSyncFileItemStatus != SyncFileItem::Success) ||
leftSyncFileItemStatus == SyncFileItem::FatalError ||
leftSyncFileItemStatus < rightSyncFileItemStatus;
}

const auto leftSyncResultStatus = leftActivity._syncResultStatus;
const auto rightSyncResultStatus = rightActivity._syncResultStatus;

if(leftSyncResultStatus != rightSyncResultStatus) {
// We only ever use SyncResult::Error in activities
return (leftSyncResultStatus != SyncResult::Undefined &&
leftSyncResultStatus != SyncResult::Success) ||
leftSyncResultStatus == SyncResult::Error;
}

// Finally sort by time, latest first
const auto leftDateTime = leftActivity._dateTime;
const auto rightDateTime = rightActivity._dateTime;

return leftDateTime > rightDateTime;
}

}
49 changes: 49 additions & 0 deletions src/gui/tray/sortedactivitylistmodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (C) by Claudio Cambra <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/

#ifndef SORTEDACTIVITYMODEL_H
#define SORTEDACTIVITYMODEL_H

#include <QSortFilterProxyModel>

namespace OCC {

class ActivityListModel;

class SortedActivityListModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(ActivityListModel* activityListModel READ activityListModel WRITE setActivityListModel NOTIFY activityListModelChanged)

public:
explicit SortedActivityListModel(QObject *parent = nullptr);

ActivityListModel *activityListModel() const;

signals:
void activityListModelChanged();

public slots:
void setActivityListModel(ActivityListModel *activityListModel);

protected:
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;

private slots:
void sortModel();
};

}

#endif // SORTEDACTIVITYMODEL_H
Loading

0 comments on commit 6136b34

Please sign in to comment.