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