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 fee5eaef61bac..8af7ed19ab9ba 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 b43cb45c016f2..b16da6f33184b 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 90b8d74c8b6c8..4272b20b200a6 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 eveything 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