From 1e9fa0a1322e597bbcc0718960d6a0e27c74ff18 Mon Sep 17 00:00:00 2001 From: alex-z Date: Fri, 8 Sep 2023 11:11:43 +0200 Subject: [PATCH] Activity list fixes and improvements. Adjusted sorting to show interactive and security activities always on top (after errors). Added button to scroll up when new activity arrives. Improved sync status scrollbar. Signed-off-by: alex-z --- resources.qrc | 1 + src/gui/folderman.cpp | 2 +- src/gui/tray/ActivityList.qml | 26 ++++++++ src/gui/tray/NCProgressBar.qml | 44 ++++++++++++ src/gui/tray/SyncStatus.qml | 31 ++------- src/gui/tray/Window.qml | 70 +++++++++++++++++++ src/gui/tray/activitylistmodel.cpp | 11 ++- src/gui/tray/activitylistmodel.h | 2 + src/gui/tray/sortedactivitylistmodel.cpp | 53 +++++++++++++++ src/gui/tray/syncstatussummary.cpp | 39 +++++++++-- src/gui/tray/syncstatussummary.h | 5 ++ src/gui/tray/usermodel.cpp | 7 +- test/activitylistmodeltestutils.cpp | 16 +++++ test/activitylistmodeltestutils.h | 2 +- test/testsortedactivitylistmodel.cpp | 85 +++++++++++++++++------- theme.qrc.in | 1 + theme/Style/Style.qml | 27 ++++++++ theme/black/expand-less-black.svg | 1 + 18 files changed, 361 insertions(+), 62 deletions(-) create mode 100644 src/gui/tray/NCProgressBar.qml create mode 100644 theme/black/expand-less-black.svg diff --git a/resources.qrc b/resources.qrc index 7a53e12e65106..afe9e7836f046 100644 --- a/resources.qrc +++ b/resources.qrc @@ -49,6 +49,7 @@ src/gui/tray/EditFileLocallyLoadingDialog.qml src/gui/tray/NCBusyIndicator.qml src/gui/tray/NCToolTip.qml + src/gui/tray/NCProgressBar.qml src/gui/tray/EnforcedPlainTextLabel.qml theme/Style/Style.qml theme/Style/qmldir diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp index 1a0ce1a084fe0..3d301f323efe3 100644 --- a/src/gui/folderman.cpp +++ b/src/gui/folderman.cpp @@ -1314,7 +1314,7 @@ QStringList FolderMan::findFileInLocalFolders(const QString &relPath, const Acco if (acc && folder->accountState()->account() != acc) { continue; } - if (!serverPath.startsWith(folder->remotePath())) + if (!serverPath.startsWith(folder->remotePathTrailingSlash())) continue; QString path = folder->cleanPath() + '/'; diff --git a/src/gui/tray/ActivityList.qml b/src/gui/tray/ActivityList.qml index 1819f421dbc16..150e6483b3c46 100644 --- a/src/gui/tray/ActivityList.qml +++ b/src/gui/tray/ActivityList.qml @@ -7,10 +7,19 @@ import com.nextcloud.desktopclient 1.0 as NC ScrollView { id: controlRoot property alias model: sortedActivityList.sourceModel + property alias count: activityList.count + property alias atYBeginning : activityList.atYBeginning property bool isFileActivityList: false property int iconSize: Style.trayListItemIconSize property int delegateHorizontalPadding: 0 + property bool scrollingToTop: false + + function scrollToTop() { + // Triggers activation of repeating upward flick timer + scrollingToTop = true + } + signal openFile(string filePath) signal activityItemClicked(int index) @@ -22,6 +31,9 @@ ScrollView { data: NC.WheelHandler { target: controlRoot.contentItem + onWheel: { + scrollingToTop = false + } } ListView { @@ -36,6 +48,20 @@ ScrollView { currentIndex: -1 interactive: true + Timer { + id: repeatUpFlickTimer + interval: Style.activityListScrollToTopTimerInterval + running: controlRoot.scrollingToTop + repeat: true + onTriggered: { + if (!activityList.atYBeginning) { + activityList.flick(0, Style.activityListScrollToTopVelocity) + } else { + controlRoot.scrollingToTop = false + } + } + } + highlight: Rectangle { id: activityHover anchors.fill: activityList.currentItem diff --git a/src/gui/tray/NCProgressBar.qml b/src/gui/tray/NCProgressBar.qml new file mode 100644 index 0000000000000..da154d430a958 --- /dev/null +++ b/src/gui/tray/NCProgressBar.qml @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023 by Oleksandr Zolotov + * + * 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. + */ + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import Style 1.0 + +ProgressBar { + id: control + + background: Rectangle { + implicitWidth: Style.progressBarWidth + implicitHeight: Style.progressBarBackgroundHeight + radius: Style.progressBarRadius + color: Style.progressBarBackgroundColor + border.color: Style.progressBarBackgroundBorderColor + border.width: Style.progressBarBackgroundBorderWidth + } + + contentItem: Item { + implicitWidth: Style.progressBarWidth + implicitHeight: Style.progressBarContentHeight + + Rectangle { + width: control.visualPosition * parent.width + height: parent.height + radius: Style.progressBarRadius + color: Style.progressBarContentColor + border.color: Style.progressBarContentBorderColor + border.width: Style.progressBarContentBorderWidth + } + } +} diff --git a/src/gui/tray/SyncStatus.qml b/src/gui/tray/SyncStatus.qml index 1dcd5cb136b8c..bbec0e4eac0d9 100644 --- a/src/gui/tray/SyncStatus.qml +++ b/src/gui/tray/SyncStatus.qml @@ -61,36 +61,13 @@ RowLayout { Loader { Layout.fillWidth: true + Layout.preferredHeight: Style.progressBarPreferredHeight - active: syncStatus.syncing - visible: syncStatus.syncing + active: syncStatus.syncing && syncStatus.totalFiles > 0 + visible: active - sourceComponent: ProgressBar { + sourceComponent: NCProgressBar { id: syncProgressBar - - // TODO: Rather than setting all these palette colours manually, - // create a custom style and do it for all components globally. - // - // Additionally, we need to override the entire palette when we - // set one palette property, as otherwise we default back to the - // theme palette -- not the parent palette - palette { - text: Style.ncTextColor - windowText: Style.ncTextColor - buttonText: Style.ncTextColor - brightText: Style.ncTextBrightColor - highlight: Style.lightHover - highlightedText: Style.ncTextColor - light: Style.lightHover - midlight: Style.ncSecondaryTextColor - mid: Style.darkerHover - dark: Style.menuBorder - button: Style.buttonBackgroundColor - window: palette.dark // NOTE: Fusion theme uses darker window colour for the border of the progress bar - base: Style.backgroundColor - toolTipBase: Style.backgroundColor - toolTipText: Style.ncTextColor - } value: syncStatus.syncProgress } } diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index adbf1480ffdb3..ce024c0d998ba 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -851,7 +851,69 @@ ApplicationWindow { anchors.right: trayWindowMainItem.right } + Loader { + id: newActivitiesButtonLoader + + anchors.top: activityList.top + anchors.topMargin: 5 + anchors.horizontalCenter: activityList.horizontalCenter + + width: Style.newActivitiesButtonWidth + height: Style.newActivitiesButtonHeight + + z: 1 + + active: false + + sourceComponent: CustomButton { + id: newActivitiesButton + hoverEnabled: true + padding: Style.smallSpacing + + textColor: Style.currentUserHeaderTextColor + textColorHovered: Style.currentUserHeaderTextColor + contentsFont.bold: true + bgNormalColor: Qt.lighter(bgHoverColor, 1.25) + bgHoverColor: Style.currentUserHeaderColor + bgNormalOpacity: Style.newActivitiesBgNormalOpacity + bgHoverOpacity: Style.newActivitiesBgHoverOpacity + + anchors.fill: parent + + text: qsTr("New activities") + + icon.source: "image://svgimage-custom-color/expand-less-black.svg" + "/" + Style.currentUserHeaderTextColor + icon.width: Style.activityLabelBaseWidth + icon.height: Style.activityLabelBaseWidth + + onClicked: { + activityList.scrollToTop(); + newActivitiesButtonLoader.active = false + } + + Timer { + id: newActivitiesButtonDisappearTimer + interval: Style.newActivityButtonDisappearTimeout + running: newActivitiesButtonLoader.active && !newActivitiesButton.hovered + repeat: false + onTriggered: fadeoutActivitiesButtonDisappear.running = true + } + + OpacityAnimator { + id: fadeoutActivitiesButtonDisappear + target: newActivitiesButton; + from: 1; + to: 0; + duration: Style.newActivityButtonDisappearFadeTimeout + loops: 1 + running: false + onFinished: newActivitiesButtonLoader.active = false + } + } + } + ActivityList { + id: activityList visible: !trayWindowMainItem.isUnifiedSearchActive anchors.top: syncStatus.bottom anchors.left: trayWindowMainItem.left @@ -864,6 +926,14 @@ ApplicationWindow { onActivityItemClicked: { model.slotTriggerDefaultAction(index) } + Connections { + target: activityModel + onInteractiveActivityReceived: { + if (!activityList.atYBeginning) { + newActivitiesButtonLoader.active = true; + } + } + } } } // Item trayWindowMainItem } diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index e4ed27f0d38f3..09336e0c8a443 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -156,7 +156,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const if (!fileName.isEmpty()) { const auto folder = FolderMan::instance()->folder(a._folder); - const QString relPath = folder ? folder->remotePath() + fileName : fileName; + const QString relPath = folder ? folder->remotePathTrailingSlash() + fileName : fileName; const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); @@ -184,7 +184,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const if (!a._file.isEmpty()) { const auto folder = FolderMan::instance()->folder(a._folder); - QString relPath = folder ? folder->remotePath() + a._file : a._file; + QString relPath = folder ? folder->remotePathTrailingSlash() + a._file : a._file; const auto localFiles = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); @@ -636,6 +636,13 @@ void ActivityListModel::addNotificationToActivityList(const Activity &activity) qCDebug(lcActivity) << "Notification successfully added to the notification list: " << activity._subject; addEntriesToActivityList({activity}); _notificationLists.prepend(activity); + for (const auto &link : activity._links) { + if (link._verb == QByteArrayLiteral("POST") + || link._verb == QByteArrayLiteral("REPLY") + || link._verb == QByteArrayLiteral("WEB")) { + emit interactiveActivityReceived(); + } + } } void ActivityListModel::addSyncFileItemToActivityList(const Activity &activity) diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 6335e8413f185..6251a12e9a1ff 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -151,6 +151,8 @@ public slots: void activityJobStatusCode(int statusCode); void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); + void interactiveActivityReceived(); + protected: [[nodiscard]] bool currentlyFetching() const; diff --git a/src/gui/tray/sortedactivitylistmodel.cpp b/src/gui/tray/sortedactivitylistmodel.cpp index d62ad4985bfae..fd8712cb8d2bf 100644 --- a/src/gui/tray/sortedactivitylistmodel.cpp +++ b/src/gui/tray/sortedactivitylistmodel.cpp @@ -13,9 +13,37 @@ */ #include "activitylistmodel.h" +#include #include "sortedactivitylistmodel.h" +namespace +{ + struct ActivityLinksSearchResult { + bool hasPOST = false; + bool hasREPLY = false; + bool hasWEB = false; + bool hasDELETE = false; + }; + + ActivityLinksSearchResult searchForVerbsInLinks(const QVector &links) + { + ActivityLinksSearchResult result; + for (const auto &link : links) { + if (link._verb == QByteArrayLiteral("POST")) { + result.hasPOST = true; + } else if (link._verb == QByteArrayLiteral("REPLY")) { + result.hasREPLY = true; + } else if (link._verb == QByteArrayLiteral("WEB")) { + result.hasWEB = true; + } else if (link._verb == QByteArrayLiteral("DELETE")) { + result.hasDELETE = true; + } + } + return result; + } +} + namespace OCC { SortedActivityListModel::SortedActivityListModel(QObject *parent) @@ -44,6 +72,31 @@ bool SortedActivityListModel::lessThan(const QModelIndex &sourceLeft, const QMod return false; } + const auto leftActivityVerbsSearchResult = searchForVerbsInLinks(leftActivity._links); + const auto rightActivityVerbsSearchResult = searchForVerbsInLinks(rightActivity._links); + + if (leftActivityVerbsSearchResult.hasPOST != rightActivityVerbsSearchResult.hasPOST) { + return leftActivityVerbsSearchResult.hasPOST; + } + + if (leftActivityVerbsSearchResult.hasREPLY != rightActivityVerbsSearchResult.hasREPLY) { + return leftActivityVerbsSearchResult.hasREPLY; + } + + if (leftActivityVerbsSearchResult.hasWEB != rightActivityVerbsSearchResult.hasWEB) { + return leftActivityVerbsSearchResult.hasWEB; + } + + if (leftActivityVerbsSearchResult.hasDELETE != rightActivityVerbsSearchResult.hasDELETE) { + return leftActivityVerbsSearchResult.hasDELETE; + } + + const auto leftActivityIsSecurityAction = leftActivity._fileAction == QStringLiteral("security"); + const auto rightActivityIsSecurityAction = rightActivity._fileAction == QStringLiteral("security"); + if (leftActivityIsSecurityAction != rightActivityIsSecurityAction) { + return leftActivityIsSecurityAction; + } + // Let's now check for errors as we want those near the top too // Sync result errors go first const auto leftSyncResultStatus = leftActivity._syncResultStatus; diff --git a/src/gui/tray/syncstatussummary.cpp b/src/gui/tray/syncstatussummary.cpp index 8f74985c8e6d3..987e725e9aef2 100644 --- a/src/gui/tray/syncstatussummary.cpp +++ b/src/gui/tray/syncstatussummary.cpp @@ -121,6 +121,7 @@ void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) { if (_accountState && !_accountState->isConnected()) { setSyncing(false); + setTotalFiles(0); setSyncStatusString(tr("Offline")); setSyncStatusDetailString(""); setSyncIcon(Theme::instance()->folderOffline()); @@ -135,6 +136,7 @@ void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) // Success should only be shown if all folders were fine if (!folderErrors() || folderError(folder)) { setSyncing(false); + setTotalFiles(0); setSyncStatusString(tr("All synced!")); setSyncStatusDetailString(""); setSyncIcon(Theme::instance()->syncStatusOk()); @@ -144,6 +146,7 @@ void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) case SyncResult::Error: case SyncResult::SetupError: setSyncing(false); + setTotalFiles(0); setSyncStatusString(tr("Some files couldn't be synced!")); setSyncStatusDetailString(tr("See below for errors")); setSyncIcon(Theme::instance()->syncStatusError()); @@ -152,13 +155,18 @@ void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) case SyncResult::SyncRunning: case SyncResult::NotYetStarted: setSyncing(true); - setSyncStatusString(tr("Syncing")); + if (totalFiles() <= 0) { + setSyncStatusString(tr("Preparing sync")); + } else { + setSyncStatusString(tr("Syncing")); + } setSyncStatusDetailString(""); setSyncIcon(Theme::instance()->syncStatusRunning()); break; case SyncResult::Paused: case SyncResult::SyncAbortRequested: setSyncing(false); + setTotalFiles(0); setSyncStatusString(tr("Sync paused")); setSyncStatusDetailString(""); setSyncIcon(Theme::instance()->syncStatusPause()); @@ -166,6 +174,7 @@ void SyncStatusSummary::setSyncStateForFolder(const Folder *folder) case SyncResult::Problem: case SyncResult::Undefined: setSyncing(false); + setTotalFiles(0); setSyncStatusString(tr("Some files could not be synced!")); setSyncStatusDetailString(tr("See below for warnings")); setSyncIcon(Theme::instance()->syncStatusWarning()); @@ -205,9 +214,15 @@ void SyncStatusSummary::onFolderProgressInfo(const ProgressInfo &progress) const qint64 currentFile = progress.currentFile(); const qint64 completedFile = progress.completedFiles(); const qint64 totalSize = qMax(completedSize, progress.totalSize()); - const qint64 totalFileCount = qMax(currentFile, progress.totalFiles()); + const qint64 numFilesInProgress = qMax(currentFile, progress.totalFiles()); + + if (_totalFiles <= 0 && numFilesInProgress > 0) { + setSyncStatusString(tr("Syncing")); + } - setSyncProgress(calculateOverallPercent(totalFileCount, completedFile, totalSize, completedSize)); + setTotalFiles(numFilesInProgress); + + setSyncProgress(calculateOverallPercent(numFilesInProgress, completedFile, totalSize, completedSize)); if (totalSize > 0) { const auto completedSizeString = Utility::octetsToString(completedSize); @@ -223,8 +238,8 @@ void SyncStatusSummary::onFolderProgressInfo(const ProgressInfo &progress) } } - if (totalFileCount > 0) { - setSyncStatusString(tr("Syncing file %1 of %2").arg(currentFile).arg(totalFileCount)); + if (numFilesInProgress > 0) { + setSyncStatusString(tr("Syncing file %1 of %2").arg(currentFile).arg(numFilesInProgress)); } } @@ -238,6 +253,14 @@ void SyncStatusSummary::setSyncing(bool value) emit syncingChanged(); } +void SyncStatusSummary::setTotalFiles(const qint64 value) +{ + if (value != _totalFiles) { + _totalFiles = value; + emit totalFilesChanged(); + } +} + void SyncStatusSummary::setSyncProgress(double value) { if (_progress == value) { @@ -268,6 +291,11 @@ QString SyncStatusSummary::syncStatusDetailString() const return _syncStatusDetailString; } +qint64 SyncStatusSummary::totalFiles() const +{ + return _totalFiles; +} + void SyncStatusSummary::setSyncIcon(const QUrl &value) { if (_syncIcon == value) { @@ -308,6 +336,7 @@ void SyncStatusSummary::onIsConnectedChanged() void SyncStatusSummary::setSyncStateToConnectedState() { setSyncing(false); + setTotalFiles(0); setSyncStatusDetailString(""); if (_accountState && !_accountState->isConnected()) { setSyncStatusString(tr("Offline")); diff --git a/src/gui/tray/syncstatussummary.h b/src/gui/tray/syncstatussummary.h index 60138350879a2..ced46fce75f3d 100644 --- a/src/gui/tray/syncstatussummary.h +++ b/src/gui/tray/syncstatussummary.h @@ -35,6 +35,7 @@ class SyncStatusSummary : public QObject Q_PROPERTY(bool syncing READ syncing NOTIFY syncingChanged) Q_PROPERTY(QString syncStatusString READ syncStatusString NOTIFY syncStatusStringChanged) Q_PROPERTY(QString syncStatusDetailString READ syncStatusDetailString NOTIFY syncStatusDetailStringChanged) + Q_PROPERTY(qint64 totalFiles READ totalFiles NOTIFY totalFilesChanged) public: explicit SyncStatusSummary(QObject *parent = nullptr); @@ -44,6 +45,7 @@ class SyncStatusSummary : public QObject [[nodiscard]] bool syncing() const; [[nodiscard]] QString syncStatusString() const; [[nodiscard]] QString syncStatusDetailString() const; + [[nodiscard]] qint64 totalFiles() const; signals: void syncProgressChanged(); @@ -51,6 +53,7 @@ class SyncStatusSummary : public QObject void syncingChanged(); void syncStatusStringChanged(); void syncStatusDetailStringChanged(); + void totalFilesChanged(); public slots: void load(); @@ -79,6 +82,7 @@ public slots: void setSyncStatusDetailString(const QString &value); void setSyncIcon(const QUrl &value); void setAccountState(AccountStatePtr accountState); + void setTotalFiles(const qint64 value); AccountStatePtr _accountState; std::set _foldersWithErrors; @@ -86,6 +90,7 @@ public slots: QUrl _syncIcon = Theme::instance()->syncStatusOk(); double _progress = 1.0; bool _isSyncing = false; + qint64 _totalFiles = 0; QString _syncStatusString = tr("All synced!"); QString _syncStatusDetailString; }; diff --git a/src/gui/tray/usermodel.cpp b/src/gui/tray/usermodel.cpp index 896ff85e4d864..da175d3e28bf8 100644 --- a/src/gui/tray/usermodel.cpp +++ b/src/gui/tray/usermodel.cpp @@ -756,6 +756,11 @@ bool User::isUnsolvableConflict(const SyncFileItemPtr &item) const void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item) { + if (item->_direction == SyncFileItem::Down && item->_instruction == CSYNC_INSTRUCTION_SYNC) { + qCDebug(lcActivity) << "Skipping activities about changes coming from server."; + return; + } + const auto fileActionFromInstruction = [](const int instruction) { if (instruction == CSYNC_INSTRUCTION_REMOVE) { return QStringLiteral("file_deleted"); @@ -806,7 +811,7 @@ void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr } if(activity._fileAction != "file_deleted" && !item->isEmpty()) { - const auto localFiles = FolderMan::instance()->findFileInLocalFolders(item->_file, account()); + const auto localFiles = FolderMan::instance()->findFileInLocalFolders(folder->remotePathTrailingSlash() + item->_file, account()); if (!localFiles.isEmpty()) { const auto firstFilePath = localFiles.constFirst(); const auto itemJournalRecord = item->toSyncJournalFileRecordWithInode(firstFilePath); diff --git a/test/activitylistmodeltestutils.cpp b/test/activitylistmodeltestutils.cpp index 4700a6b6d3e4d..69c793895f730 100644 --- a/test/activitylistmodeltestutils.cpp +++ b/test/activitylistmodeltestutils.cpp @@ -387,6 +387,22 @@ void FakeRemoteActivityStorage::initActivityData() _startingId++; } + // Insert notification data + for (quint32 i = 0; i < _numItemsToInsert; i++) { + QJsonObject activity; + activity.insert(QStringLiteral("activity_id"), _startingId); + activity.insert(QStringLiteral("object_type"), QStringLiteral("")); + activity.insert(QStringLiteral("type"), QStringLiteral("security")); + activity.insert(QStringLiteral("subject"), QStringLiteral("You successfully logged in using two-factor authentication (Nextcloud Notification)")); + activity.insert(QStringLiteral("message"), QStringLiteral("")); + activity.insert(QStringLiteral("object_name"), QStringLiteral("")); + activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate)); + activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/password.svg")); + + _activityData.push_back(activity); + + _startingId++; + } _startingId--; } diff --git a/test/activitylistmodeltestutils.h b/test/activitylistmodeltestutils.h index 55310f197b925..a68d72481eb77 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -64,7 +64,7 @@ class FakeRemoteActivityStorage private: QJsonArray _activityData; QVariantMap _metaSuccess; - quint32 _numItemsToInsert = 30; + quint32 _numItemsToInsert = 10; int _startingId = 90000; static FakeRemoteActivityStorage *_instance; diff --git a/test/testsortedactivitylistmodel.cpp b/test/testsortedactivitylistmodel.cpp index 2491227e53001..6af961964d1fd 100644 --- a/test/testsortedactivitylistmodel.cpp +++ b/test/testsortedactivitylistmodel.cpp @@ -153,7 +153,7 @@ private slots: sourceModel->startMaxActivitiesFetchJob(); QSignalSpy activitiesJob(sourceModel, &TestingALM::activitiesProcessed); QVERIFY(activitiesJob.wait(3000)); - QCOMPARE(sourceModel->rowCount(), sourceModel->maxPossibleActivities()); + QCOMPARE(sourceModel->rowCount(), FakeRemoteActivityStorage::instance()->totalNumActivites()); auto errorSyncFileItemActivity = exampleSyncFileItemActivity(accountState->account()->displayName(), {}); errorSyncFileItemActivity._message = QStringLiteral("Something went wrong and everything exploded!"); @@ -165,38 +165,73 @@ private slots: addActivity(model, &TestingALM::addErrorToActivityList, testSyncResultErrorActivity, OCC::ActivityListModel::ErrorType::SyncError); addActivity(model, &TestingALM::addIgnoredFileToList, testFileIgnoredActivity); - const QVector activityDefaultTypeOrder { - OCC::Activity::DummyFetchingActivityType, - OCC::Activity::NotificationType, - OCC::Activity::SyncResultType, - OCC::Activity::SyncFileItemType, - OCC::Activity::ActivityType, - OCC::Activity::DummyMoreActivitiesAvailableType}; + // first let's go through priority activities (interactive ones and those with _fileAction == "security" + auto i = 0; + for (; i < model->rowCount(); ++i) { + const auto index = model->index(i, 0); + const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); + + const auto foundIt = std::find_if(std::cbegin(activity._links), std::cend(activity._links), [](const auto &link) { + return link._verb == QByteArrayLiteral("POST") || link._verb == QByteArrayLiteral("REPLY") || link._verb == QByteArrayLiteral("WEB") + || link._verb == QByteArrayLiteral("DELETE"); + }); + const auto isInteractiveOrSecurityActivity = foundIt != std::cend(activity._links) || activity._fileAction == QStringLiteral("security"); + if (!isInteractiveOrSecurityActivity) { + break; + } + } + auto lasIndex = i; + + // now, let's check if activity is an error + for (; i < lasIndex + 1 && i < model->rowCount(); ++i) { + const auto index = model->index(i, 0); + const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); + + QCOMPARE(activity._type, OCC::Activity::SyncResultType); + QCOMPARE(activity._syncResultStatus, OCC::SyncResult::Error); + } + lasIndex = i; + + // now, let's check if activity is a fatal error + for (; i < lasIndex + 1 && i < model->rowCount(); ++i) { + const auto index = model->index(i, 0); + const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); + + QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); + QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FatalError); + } + lasIndex = i; + + // now, let's check if activity is an ignored file + for (; i < lasIndex + 1 && i < model->rowCount(); ++i) { + const auto index = model->index(i, 0); + const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); + QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); + QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FileIgnored); + } + lasIndex = i; + + const QVector activityDefaultTypeOrder{OCC::Activity::DummyFetchingActivityType, + OCC::Activity::SyncResultType, + OCC::Activity::NotificationType, + OCC::Activity::SyncFileItemType, + OCC::Activity::ActivityType, + OCC::Activity::DummyMoreActivitiesAvailableType}; auto currentTypeSection = 1; auto previousType = activityDefaultTypeOrder[currentTypeSection]; - for (auto i = 0; i < model->rowCount(); ++i) { + // let's go through rest of activities (Now normal type order) + for (; i < model->rowCount(); ++i) { const auto index = model->index(i, 0); const auto activity = index.data(OCC::ActivityListModel::ActivityRole).value(); qDebug() << i << activity._type << activity._subject << activity._message; - if (i == 0) { // Error syncresult activity should be at top - QCOMPARE(activity._type, OCC::Activity::SyncResultType); - QCOMPARE(activity._syncResultStatus, OCC::SyncResult::Error); - } else if (i == 1) { // Error syncfileitem activity should be next up - QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); - QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FatalError); - } else if (i == 2) { // Ignored file syncfileitem activity should be next up - QCOMPARE(activity._type, OCC::Activity::SyncFileItemType); - QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FileIgnored); - } else { // Now normal type order - while (i != 3 && activity._type != previousType) { - ++currentTypeSection; - previousType = activityDefaultTypeOrder[currentTypeSection]; - } - - QCOMPARE(activity._type, activityDefaultTypeOrder[currentTypeSection]); + + while (activity._type != previousType) { + ++currentTypeSection; + previousType = activityDefaultTypeOrder[currentTypeSection]; } + QCOMPARE(activity._type, activityDefaultTypeOrder[currentTypeSection]); } } }; diff --git a/theme.qrc.in b/theme.qrc.in index 8d6996aeea74b..60f25c4fca1ba 100644 --- a/theme.qrc.in +++ b/theme.qrc.in @@ -95,6 +95,7 @@ theme/black/confirm.svg theme/black/control-next.svg theme/black/control-prev.svg + theme/black/expand-less-black.svg theme/black/settings.svg theme/black/state-error.svg theme/black/state-error-16.png diff --git a/theme/Style/Style.qml b/theme/Style/Style.qml index a67cb30447143..ae9abf7801f50 100644 --- a/theme/Style/Style.qml +++ b/theme/Style/Style.qml @@ -158,6 +158,33 @@ QtObject { readonly property int shortAnimationDuration: 200 readonly property int veryLongAnimationDuration: 3000 + // sync status + property int progressBarPreferredHeight: 9 + + property int progressBarWidth: 100 + property int progressBarBackgroundHeight: 8 + property int progressBarContentHeight: 8 + property int progressBarRadius: 4 + property int progressBarContentBorderWidth: 1 + property int progressBarBackgroundBorderWidth: 1 + property color progressBarContentColor: ncBlue + property color progressBarContentBorderColor: menuBorder + property color progressBarBackgroundColor: backgroundColor + property color progressBarBackgroundBorderColor: menuBorder + + property int newActivitiesButtonWidth: 150 + property int newActivitiesButtonHeight: 40 + + property real newActivitiesBgNormalOpacity: 0.8 + property real newActivitiesBgHoverOpacity: 1.0 + + property int newActivityButtonDisappearTimeout: 5000 + property int newActivityButtonDisappearFadeTimeout: 250 + + property int activityListScrollToTopTimerInterval: 50 + + property int activityListScrollToTopVelocity: 10000 + function variableSize(size) { return size * (1 + Math.min(pixelSize / 100, 1)); } diff --git a/theme/black/expand-less-black.svg b/theme/black/expand-less-black.svg new file mode 100644 index 0000000000000..72c48c9f40db9 --- /dev/null +++ b/theme/black/expand-less-black.svg @@ -0,0 +1 @@ + \ No newline at end of file