From: Felix Weilbach Date: Fri, 20 Aug 2021 13:17:06 +0000 (+0200) Subject: Add file activity dialog X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~18^2~189^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=0c9dce1154ee61bc747909ba3599c41dd8039ada;p=nextcloud-desktop.git Add file activity dialog Signed-off-by: Felix Weilbach --- diff --git a/resources.qrc b/resources.qrc index f811db0f7..0b95c4274 100644 --- a/resources.qrc +++ b/resources.qrc @@ -12,5 +12,7 @@ src/gui/tray/ActivityActionButton.qml src/gui/tray/ActivityItem.qml src/gui/tray/AutoSizingMenu.qml + src/gui/tray/ActivityList.qml + src/gui/tray/FileActivityDialog.qml diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 94894046d..a1537c2bc 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -40,6 +40,7 @@ set(client_UI_SRCS UserStatusSelectorDialog.qml tray/ActivityActionButton.qml tray/ActivityItem.qml + tray/ActivityList.qml tray/Window.qml tray/UserLine.qml wizard/flow2authwidget.ui @@ -111,6 +112,7 @@ set(client_SRCS remotewipe.cpp userstatusselectormodel.cpp emojimodel.cpp + fileactivitylistmodel.cpp tray/ActivityData.cpp tray/ActivityListModel.cpp tray/UserModel.cpp diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 64b2da8fa..b02e0980f 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -349,6 +349,9 @@ Application::Application(int &argc, char **argv) connect(FolderMan::instance()->socketApi(), &SocketApi::shareCommandReceived, _gui.data(), &ownCloudGui::slotShowShareDialog); + connect(FolderMan::instance()->socketApi(), &SocketApi::fileActivityCommandReceived, + Systray::instance(), &Systray::showFileActivityDialog); + // startup procedure. connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection); _checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); // check for connection every 32 seconds. diff --git a/src/gui/fileactivitylistmodel.cpp b/src/gui/fileactivitylistmodel.cpp new file mode 100644 index 000000000..08e916252 --- /dev/null +++ b/src/gui/fileactivitylistmodel.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) by Felix Weilbach + * + * 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 "fileactivitylistmodel.h" +#include "folderman.h" +#include "tray/ActivityListModel.h" + +namespace OCC { + +Q_LOGGING_CATEGORY(lcFileActivityListModel, "nextcloud.gui.fileactivitylistmodel", QtInfoMsg) + +FileActivityListModel::FileActivityListModel(QObject *parent) + : ActivityListModel(nullptr, parent) +{ + setDisplayActions(false); +} + +void FileActivityListModel::load(AccountState *accountState, const QString &localPath) +{ + Q_ASSERT(accountState); + if (!accountState || currentlyFetching()) { + return; + } + setAccountState(accountState); + + const auto folder = FolderMan::instance()->folderForPath(localPath); + if (!folder) { + return; + } + + const auto file = folder->fileFromLocalPath(localPath); + SyncJournalFileRecord fileRecord; + if (!folder->journalDb()->getFileRecord(file, &fileRecord) || !fileRecord.isValid()) { + return; + } + + _fileId = fileRecord._fileId; + slotRefreshActivity(); +} + +void FileActivityListModel::startFetchJob() +{ + if (!accountState()->isConnected()) { + return; + } + setCurrentlyFetching(true); + + const QString url(QStringLiteral("ocs/v2.php/apps/activity/api/v2/activity/filter")); + auto job = new JsonApiJob(accountState()->account(), url, this); + QObject::connect(job, &JsonApiJob::jsonReceived, + this, &FileActivityListModel::activitiesReceived); + + QUrlQuery params; + params.addQueryItem(QStringLiteral("sort"), QStringLiteral("asc")); + params.addQueryItem(QStringLiteral("object_type"), "files"); + params.addQueryItem(QStringLiteral("object_id"), _fileId); + job->addQueryParams(params); + setDoneFetching(true); + setHideOldActivities(true); + job->start(); +} +} diff --git a/src/gui/fileactivitylistmodel.h b/src/gui/fileactivitylistmodel.h new file mode 100644 index 000000000..2c465dcf2 --- /dev/null +++ b/src/gui/fileactivitylistmodel.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) by Felix Weilbach + * + * 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. + */ + +#pragma once + +#include "accountstate.h" +#include "tray/ActivityListModel.h" + +namespace OCC { + +class FileActivityListModel : public ActivityListModel +{ + Q_OBJECT + +public: + explicit FileActivityListModel(QObject *parent = nullptr); + +public slots: + void load(AccountState *accountState, const QString &fileId); + +protected: + void startFetchJob() override; + +private: + QString _fileId; +}; +} diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 0cb4f472b..993dcb69e 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -1261,6 +1261,11 @@ void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction dir, std::functio msgBox->open(); } +QString Folder::fileFromLocalPath(const QString &localPath) const +{ + return localPath.mid(cleanPath().length() + 1); +} + void FolderDefinition::save(QSettings &settings, const FolderDefinition &folder) { settings.setValue(QLatin1String("localPath"), folder.localPath); @@ -1354,4 +1359,5 @@ QString FolderDefinition::defaultJournalPath(AccountPtr account) return SyncJournalDb::makeDbName(localPath, account->url(), targetPath, account->credentials()->user()); } + } // namespace OCC diff --git a/src/gui/folder.h b/src/gui/folder.h index f87e028bf..6efdd1eb0 100644 --- a/src/gui/folder.h +++ b/src/gui/folder.h @@ -290,6 +290,8 @@ public: /** Whether this folder should show selective sync ui */ bool supportsSelectiveSync() const; + QString fileFromLocalPath(const QString &localPath) const; + signals: void syncStateChange(); void syncStarted(); diff --git a/src/gui/main.cpp b/src/gui/main.cpp index ef5747974..71e53abbe 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -24,6 +24,7 @@ #endif #include "application.h" +#include "fileactivitylistmodel.h" #include "theme.h" #include "common/utility.h" #include "cocoainitializer.h" @@ -59,11 +60,13 @@ int main(int argc, char **argv) Q_INIT_RESOURCE(theme); qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "EmojiModel"); + qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "UserStatusSelectorModel"); + qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "ActivityListModel"); + qmlRegisterType("com.nextcloud.desktopclient", 1, 0, "FileActivityListModel"); + + qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum"); + qRegisterMetaTypeStreamOperators(); - qmlRegisterType("com.nextcloud.desktopclient", 1, 0, - "UserStatusSelectorModel"); - qmlRegisterUncreatableType("com.nextcloud.desktopclient", 1, 0, "UserStatus", - "Access to Status enum"); qRegisterMetaType("UserStatus"); diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp index 0b3d37582..9be00d22f 100644 --- a/src/gui/socketapi/socketapi.cpp +++ b/src/gui/socketapi/socketapi.cpp @@ -494,6 +494,12 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait) } } +void SocketApi::processFileActivityRequest(const QString &localFile) +{ + const auto fileData = FileData::get(localFile); + emit fileActivityCommandReceived(fileData.serverRelativePath, fileData.localPath); +} + void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage) { auto theme = Theme::instance(); @@ -578,6 +584,13 @@ void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups); } +void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener) +{ + Q_UNUSED(listener); + + processFileActivityRequest(localFile); +} + void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener) { processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks); @@ -976,12 +989,13 @@ void OCC::SocketApi::openPrivateLink(const QString &link) void SocketApi::command_GET_STRINGS(const QString &argument, SocketListener *listener) { - static std::array, 5> strings { { + static std::array, 6> strings { { { "SHARE_MENU_TITLE", tr("Share options") }, + { "FILE_ACTIVITY_MENU_TITLE", tr("Activity") }, { "CONTEXT_MENU_TITLE", Theme::instance()->appNameGUI() }, { "COPY_PRIVATE_LINK_MENU_TITLE", tr("Copy private link to clipboard") }, { "EMAIL_PRIVATE_LINK_MENU_TITLE", tr("Send private link by email …") }, - { "CONTEXT_MENU_ICON", APPLICATION_ICON_NAME}, + { "CONTEXT_MENU_ICON", APPLICATION_ICON_NAME }, } }; listener->sendMessage(QString("GET_STRINGS:BEGIN")); for (const auto& key_value : strings) { @@ -1118,6 +1132,11 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe const auto isE2eEncryptedPath = fileData.journalRecord()._isE2eEncrypted || !fileData.journalRecord()._e2eMangledName.isEmpty(); auto flagString = isOnTheServer && !isE2eEncryptedPath ? QLatin1String("::") : QLatin1String(":d:"); + const QFileInfo fileInfo(fileData.localPath); + if (!fileInfo.isDir()) { + listener->sendMessage(QLatin1String("MENU_ITEM:ACTIVITY") + flagString + tr("Activity")); + } + DirectEditor* editor = getDirectEditorForLocalFile(fileData.localPath); if (editor) { //listener->sendMessage(QLatin1String("MENU_ITEM:EDIT") + flagString + tr("Edit via ") + editor->name()); @@ -1132,7 +1151,6 @@ void SocketApi::command_GET_MENU_ITEMS(const QString &argument, OCC::SocketListe bool isConflict = Utility::isConflictFile(fileData.folderRelativePath); if (isConflict || !isOnTheServer) { // Check whether this new file is in a read-only directory - QFileInfo fileInfo(fileData.localPath); const auto parentDir = fileData.parentFolder(); const auto parentRecord = parentDir.journalRecord(); const bool canAddToDir = diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h index 25837bd19..5183985b5 100644 --- a/src/gui/socketapi/socketapi.h +++ b/src/gui/socketapi/socketapi.h @@ -64,6 +64,7 @@ public slots: signals: void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage); + void fileActivityCommandReceived(const QString &sharePath, const QString &localPath); private slots: void slotNewConnection(); @@ -102,6 +103,7 @@ private: // opens share dialog, sends reply void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage); + void processFileActivityRequest(const QString &localFile); Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener); Q_INVOKABLE void command_RETRIEVE_FILE_STATUS(const QString &argument, SocketListener *listener); @@ -111,6 +113,7 @@ private: Q_INVOKABLE void command_SHARE_MENU_TITLE(const QString &argument, SocketListener *listener); // The context menu actions + Q_INVOKABLE void command_ACTIVITY(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_SHARE(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener); Q_INVOKABLE void command_COPY_PUBLIC_LINK(const QString &localFile, SocketListener *listener); diff --git a/src/gui/systray.h b/src/gui/systray.h index 8ac13c071..e6b7bdc02 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -79,6 +79,7 @@ signals: void hideWindow(); void showWindow(); void openShareDialog(const QString &sharePath, const QString &localPath); + void showFileActivityDialog(const QString &sharePath, const QString &localPath); public slots: void slotNewUserSelected(); diff --git a/src/gui/tray/ActivityItem.qml b/src/gui/tray/ActivityItem.qml index bf480a098..e84976e62 100644 --- a/src/gui/tray/ActivityItem.qml +++ b/src/gui/tray/ActivityItem.qml @@ -7,9 +7,15 @@ import com.nextcloud.desktopclient 1.0 MouseArea { id: activityMouseArea + + readonly property int maxActionButtons: 2 + property Flickable flickable + + signal fileActivityButtonClicked(string absolutePath) + enabled: (path !== "" || link !== "") hoverEnabled: true - + Rectangle { anchors.fill: parent anchors.margins: 2 @@ -106,7 +112,7 @@ MouseArea { } Repeater { - model: activityItem.links.length > activityListView.maxActionButtons ? 1 : activityItem.links.length + model: activityItem.links.length > maxActionButtons ? 1 : activityItem.links.length ActivityActionButton { id: activityActionButton @@ -139,6 +145,31 @@ MouseArea { } } + + Button { + id: shareButton + + Layout.preferredWidth: parent.height + Layout.fillHeight: true + Layout.alignment: Qt.AlignRight + flat: true + hoverEnabled: true + visible: displayActions && (path !== "") + display: AbstractButton.IconOnly + icon.source: "qrc:///client/theme/share.svg" + icon.color: "transparent" + background: Rectangle { + color: parent.hovered ? Style.lightHover : "transparent" + } + ToolTip.visible: hovered + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval + ToolTip.text: qsTr("Open share dialog") + onClicked: Systray.openShareDialog(displayPath, absolutePath) + + Accessible.role: Accessible.Button + Accessible.name: qsTr("Share %1").arg(displayPath) + Accessible.onPressAction: shareButton.clicked() + } Button { id: moreActionsButton @@ -149,7 +180,7 @@ MouseArea { flat: true hoverEnabled: true - visible: activityItem.links.length > activityListView.maxActionButtons + visible: displayActions && ((path !== "") || (activityItem.links.length > maxActionButtons)) display: AbstractButton.IconOnly icon.source: "qrc:///client/theme/more.svg" icon.color: "transparent" @@ -157,7 +188,7 @@ MouseArea { color: parent.hovered ? Style.lightHover : "transparent" } ToolTip.visible: hovered - ToolTip.delay: 1000 + ToolTip.delay: Qt.styleHints.mousePressAndHoldInterval ToolTip.text: qsTr("Show more actions") Accessible.role: Accessible.Button @@ -167,16 +198,7 @@ MouseArea { onClicked: moreActionsButtonContextMenu.popup(); Connections { - target: trayWindow - function onActiveChanged() { - if (!trayWindow.active) { - moreActionsButtonContextMenu.close(); - } - } - } - - Connections { - target: activityListView + target: flickable function onMovementStarted() { moreActionsButtonContextMenu.close(); @@ -199,7 +221,7 @@ MouseArea { // transform model to contain indexed actions with primary action filtered out function actionListToContextMenuList(actionList) { // early out with non-altered data - if (activityItem.links.length <= activityListView.maxActionButtons) { + if (activityItem.links.length <= maxActionButtons) { return actionList; } @@ -215,6 +237,11 @@ MouseArea { return reducedActionList; } + + MenuItem { + text: qsTr("View activity") + onClicked: fileActivityButtonClicked(absolutePath) + } Repeater { id: moreActionsButtonContextMenuRepeater @@ -230,31 +257,6 @@ MouseArea { } } } - - Button { - id: shareButton - - Layout.preferredWidth: (path === "") ? 0 : parent.height - Layout.preferredHeight: parent.height - Layout.alignment: Qt.AlignRight - flat: true - hoverEnabled: true - visible: (path === "") ? false : true - display: AbstractButton.IconOnly - icon.source: "qrc:///client/theme/share.svg" - icon.color: "transparent" - background: Rectangle { - color: parent.hovered ? Style.lightHover : "transparent" - } - ToolTip.visible: hovered - ToolTip.delay: 1000 - ToolTip.text: qsTr("Open share dialog") - onClicked: Systray.openShareDialog(displayPath,absolutePath) - - Accessible.role: Accessible.Button - Accessible.name: qsTr("Share %1").arg(displayPath) - Accessible.onPressAction: shareButton.clicked() - } } } } diff --git a/src/gui/tray/ActivityList.qml b/src/gui/tray/ActivityList.qml new file mode 100644 index 000000000..dc3528af9 --- /dev/null +++ b/src/gui/tray/ActivityList.qml @@ -0,0 +1,36 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +import Style 1.0 + +import com.nextcloud.desktopclient 1.0 as NC + +ScrollView { + property alias model: activityList.model + + signal showFileActivity(string displayPath, string absolutePath) + signal activityItemClicked(int index) + + contentWidth: availableWidth + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ListView { + id: activityList + + keyNavigationEnabled: true + + Accessible.role: Accessible.List + Accessible.name: qsTr("Activity list") + + clip: true + + delegate: ActivityItem { + width: activityList.contentWidth + height: Style.trayWindowHeaderHeight + flickable: activityList + onClicked: activityItemClicked(model.index) + onFileActivityButtonClicked: showFileActivity(displayPath, absolutePath) + } + } +} diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 22ce0ae52..3d69956b9 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "account.h" #include "accountstate.h" @@ -39,7 +40,13 @@ namespace OCC { Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg) -ActivityListModel::ActivityListModel(AccountState *accountState, QObject *parent) +ActivityListModel::ActivityListModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +ActivityListModel::ActivityListModel(AccountState *accountState, + QObject *parent) : QAbstractListModel(parent) , _accountState(accountState) { @@ -60,9 +67,40 @@ QHash ActivityListModel::roleNames() const roles[ActionTextColorRole] = "activityTextTitleColor"; roles[ObjectTypeRole] = "objectType"; roles[PointInTimeRole] = "dateTime"; + roles[DisplayActions] = "displayActions"; return roles; } +void ActivityListModel::setAccountState(AccountState *state) +{ + _accountState = state; +} + +void ActivityListModel::setCurrentlyFetching(bool value) +{ + _currentlyFetching = value; +} + +bool ActivityListModel::currentlyFetching() const +{ + return _currentlyFetching; +} + +void ActivityListModel::setDoneFetching(bool value) +{ + _doneFetching = value; +} + +void ActivityListModel::setHideOldActivities(bool value) +{ + _hideOldActivities = value; +} + +void ActivityListModel::setDisplayActions(bool value) +{ + _displayActions = value; +} + QVariant ActivityListModel::data(const QModelIndex &index, int role) const { Activity a; @@ -222,6 +260,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return a._id == -1 ? "" : Utility::timeAgoInWords(a._dateTime.toLocalTime()); case AccountConnectedRole: return (ast && ast->isConnected()); + case DisplayActions: + return _displayActions; default: return QVariant(); } @@ -253,7 +293,7 @@ void ActivityListModel::startFetchJob() } auto *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this); QObject::connect(job, &JsonApiJob::jsonReceived, - this, &ActivityListModel::slotActivitiesReceived); + this, &ActivityListModel::activitiesReceived); QUrlQuery params; params.addQueryItem(QLatin1String("since"), QString::number(_currentItem)); @@ -265,7 +305,7 @@ void ActivityListModel::startFetchJob() job->start(); } -void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int statusCode) +void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode) { auto activities = json.object().value("ocs").toObject().value("data").toArray(); @@ -304,9 +344,8 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st _currentItem = list.last()._id; _totalActivitiesFetched++; - if(_totalActivitiesFetched == _maxActivities || - a._dateTime < oldestDate) { - + if (_totalActivitiesFetched == _maxActivities + || (_hideOldActivities && a._dateTime < oldestDate)) { _showMoreActivitiesAvailableEntry = true; _doneFetching = true; break; @@ -496,6 +535,11 @@ void ActivityListModel::triggerAction(int activityIndex, int actionIndex) emit sendNotificationRequest(activity._accName, action._link, action._verb, activityIndex); } +AccountState *ActivityListModel::accountState() const +{ + return _accountState; +} + void ActivityListModel::combineActivityLists() { ActivityList resultList; diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index 6c67af92d..657316104 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -39,26 +39,34 @@ class InvalidFilenameDialog; class ActivityListModel : public QAbstractListModel { Q_OBJECT + + Q_PROPERTY(AccountState *accountState READ accountState CONSTANT) public: enum DataRole { - ActionIconRole = Qt::UserRole + 1, - UserIconRole, - AccountRole, - ObjectTypeRole, - ActionsLinksRole, - ActionTextRole, - ActionTextColorRole, - ActionRole, - MessageRole, - DisplayPathRole, - PathRole, - AbsolutePathRole, - LinkRole, - PointInTimeRole, - AccountConnectedRole, - SyncFileStatusRole}; - - explicit ActivityListModel(AccountState *accountState, QObject* parent = nullptr); + ActionIconRole = Qt::UserRole + 1, + UserIconRole, + AccountRole, + ObjectTypeRole, + ActionsLinksRole, + ActionTextRole, + ActionTextColorRole, + ActionRole, + MessageRole, + DisplayPathRole, + PathRole, + AbsolutePathRole, + LinkRole, + PointInTimeRole, + AccountConnectedRole, + SyncFileStatusRole, + DisplayActions, + }; + Q_ENUM(DataRole) + + explicit ActivityListModel(QObject *parent = nullptr); + + explicit ActivityListModel(AccountState *accountState, + QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -79,22 +87,30 @@ public: Q_INVOKABLE void triggerDefaultAction(int activityIndex); Q_INVOKABLE void triggerAction(int activityIndex, int actionIndex); + AccountState *accountState() const; + public slots: void slotRefreshActivity(); void slotRemoveAccount(); -private slots: - void slotActivitiesReceived(const QJsonDocument &json, int statusCode); - signals: void activityJobStatusCode(int statusCode); void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); protected: + void activitiesReceived(const QJsonDocument &json, int statusCode); QHash roleNames() const override; + void setAccountState(AccountState *state); + void setCurrentlyFetching(bool value); + bool currentlyFetching() const; + void setDoneFetching(bool value); + void setHideOldActivities(bool value); + void setDisplayActions(bool value); + + virtual void startFetchJob(); + private: - void startFetchJob(); void combineActivityLists(); bool canFetchActivities() const; @@ -105,11 +121,10 @@ private: Activity _notificationIgnoredFiles; ActivityList _notificationErrorsLists; ActivityList _finalList; - AccountState *_accountState; - bool _currentlyFetching = false; - bool _doneFetching = false; int _currentItem = 0; + bool _displayActions = true; + int _totalActivitiesFetched = 0; int _maxActivities = 100; int _maxActivitiesDays = 30; @@ -117,6 +132,11 @@ private: QPointer _currentConflictDialog; QPointer _currentInvalidFilenameDialog; + + AccountState *_accountState = nullptr; + bool _currentlyFetching = false; + bool _doneFetching = false; + bool _hideOldActivities = true; }; } diff --git a/src/gui/tray/FileActivityDialog.qml b/src/gui/tray/FileActivityDialog.qml new file mode 100644 index 000000000..7fcd653d1 --- /dev/null +++ b/src/gui/tray/FileActivityDialog.qml @@ -0,0 +1,21 @@ +import QtQuick.Window 2.15 + +import com.nextcloud.desktopclient 1.0 as NC + +Window { + id: dialog + + property alias model: activityModel + + NC.FileActivityListModel { + id: activityModel + } + + width: 500 + height: 500 + + ActivityList { + anchors.fill: parent + model: dialog.model + } +} diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 31d5143de..57a019341 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -22,8 +22,15 @@ Window { flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint + property var fileActivityDialogAbsolutePath: "" readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth + function openFileActivityDialog(displayPath, absolutePath) { + fileActivityDialogLoader.displayPath = displayPath + fileActivityDialogLoader.absolutePath = absolutePath + fileActivityDialogLoader.refresh() + } + Component.onCompleted: Systray.forceWindowInit(trayWindow) // Close tray window when focus is lost (e.g. click somewhere else on the screen) @@ -70,6 +77,10 @@ Window { trayWindow.hide(); Systray.setClosed(); } + + function onShowFileActivityDialog(displayPath, absolutePath) { + openFileActivityDialog(displayPath, absolutePath) + } } OpacityMask { @@ -553,31 +564,40 @@ Window { } } // Rectangle trayWindowHeaderBackground - ListView { - id: activityListView - anchors.top: trayWindowHeaderBackground.bottom - anchors.left: trayWindowBackground.left - anchors.right: trayWindowBackground.right - anchors.bottom: trayWindowBackground.bottom - clip: true - ScrollBar.vertical: ScrollBar { - id: listViewScrollbar + ActivityList { + anchors.top: trayWindowHeaderBackground.bottom + anchors.left: trayWindowBackground.left + anchors.right: trayWindowBackground.right + anchors.bottom: trayWindowBackground.bottom + + model: activityModel + onShowFileActivity: { + openFileActivityDialog(displayPath, absolutePath) + } + onActivityItemClicked: { + model.triggerDefaultAction(index) + } + } + + Loader { + id: fileActivityDialogLoader + + property string displayPath: "" + property string absolutePath: "" + + function refresh() { + active = true + item.model.load(activityModel.accountState, absolutePath) + item.show() } - readonly property int maxActionButtons: 2 - - keyNavigationEnabled: true - - Accessible.role: Accessible.List - Accessible.name: qsTr("Activity list") - - model: activityModel - - delegate: ActivityItem { - width: activityListView.width - height: Style.trayWindowHeaderHeight - onClicked: activityModel.triggerDefaultAction(model.index) + active: false + sourceComponent: FileActivityDialog { + title: qsTr("%1 - File activity").arg(fileActivityDialogLoader.displayPath) + onClosing: fileActivityDialogLoader.active = false } + + onLoaded: refresh() } - } // Rectangle trayWindowBackground + } // Rectangle trayWindowBackground }