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 7a53e12e6..afe9e7836 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 1a0ce1a08..3d301f323 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 1819f421d..150e6483b 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 000000000..da154d430 --- /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 1dcd5cb13..bbec0e4ea 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 adbf1480f..ce024c0d9 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 e4ed27f0d..09336e0c8 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 6335e8413..6251a12e9 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -151,6 +151,8 @@ signals: 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 d62ad4985..fd8712cb8 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 8f74985c8..987e725e9 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()); - setSyncProgress(calculateOverallPercent(totalFileCount, completedFile, totalSize, completedSize)); + if (_totalFiles <= 0 && numFilesInProgress > 0) { + setSyncStatusString(tr("Syncing")); + } + + 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 601383508..ced46fce7 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 @@ public: [[nodiscard]] bool syncing() const; [[nodiscard]] QString syncStatusString() const; [[nodiscard]] QString syncStatusDetailString() const; + [[nodiscard]] qint64 totalFiles() const; signals: void syncProgressChanged(); @@ -51,6 +53,7 @@ signals: void syncingChanged(); void syncStatusStringChanged(); void syncStatusDetailStringChanged(); + void totalFilesChanged(); public slots: void load(); @@ -79,6 +82,7 @@ private: 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 @@ private: 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 896ff85e4..da175d3e2 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 4700a6b6d..69c793895 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 55310f197..a68d72481 100644 --- a/test/activitylistmodeltestutils.h +++ b/test/activitylistmodeltestutils.h @@ -64,7 +64,7 @@ public: 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 2491227e5..6af961964 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 8d6996aee..60f25c4fc 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 a67cb3044..ae9abf780 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 000000000..72c48c9f4 --- /dev/null +++ b/theme/black/expand-less-black.svg @@ -0,0 +1 @@ + \ No newline at end of file