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 <blackslayer4@gmail.com>
This commit is contained in:
alex-z 2023-09-08 11:11:43 +02:00 committed by allexzander
parent 2660b04d47
commit 1e9fa0a132
18 changed files with 361 additions and 62 deletions

View File

@ -49,6 +49,7 @@
<file>src/gui/tray/EditFileLocallyLoadingDialog.qml</file>
<file>src/gui/tray/NCBusyIndicator.qml</file>
<file>src/gui/tray/NCToolTip.qml</file>
<file>src/gui/tray/NCProgressBar.qml</file>
<file>src/gui/tray/EnforcedPlainTextLabel.qml</file>
<file>theme/Style/Style.qml</file>
<file>theme/Style/qmldir</file>

View File

@ -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() + '/';

View File

@ -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

View File

@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 by Oleksandr Zolotov <alex@nextcloud.com>
*
* 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
}
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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;

View File

@ -13,9 +13,37 @@
*/
#include "activitylistmodel.h"
#include <QVector>
#include "sortedactivitylistmodel.h"
namespace
{
struct ActivityLinksSearchResult {
bool hasPOST = false;
bool hasREPLY = false;
bool hasWEB = false;
bool hasDELETE = false;
};
ActivityLinksSearchResult searchForVerbsInLinks(const QVector<OCC::ActivityLink> &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;

View File

@ -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"));

View File

@ -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<QString> _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;
};

View File

@ -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);

View File

@ -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--;
}

View File

@ -64,7 +64,7 @@ public:
private:
QJsonArray _activityData;
QVariantMap _metaSuccess;
quint32 _numItemsToInsert = 30;
quint32 _numItemsToInsert = 10;
int _startingId = 90000;
static FakeRemoteActivityStorage *_instance;

View File

@ -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<OCC::Activity::Type> 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<OCC::Activity>();
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<OCC::Activity>();
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<OCC::Activity>();
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<OCC::Activity>();
QCOMPARE(activity._type, OCC::Activity::SyncFileItemType);
QCOMPARE(activity._syncFileItemStatus, OCC::SyncFileItem::FileIgnored);
}
lasIndex = i;
const QVector<OCC::Activity::Type> 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<OCC::Activity>();
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]);
}
}
};

View File

@ -95,6 +95,7 @@
<file>theme/black/confirm.svg</file>
<file>theme/black/control-next.svg</file>
<file>theme/black/control-prev.svg</file>
<file>theme/black/expand-less-black.svg</file>
<file>theme/black/settings.svg</file>
<file>theme/black/state-error.svg</file>
<file>theme/black/state-error-16.png</file>

View File

@ -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));
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"/></svg>

After

Width:  |  Height:  |  Size: 203 B