mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 20:45:51 +03:00
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:
parent
2660b04d47
commit
1e9fa0a132
18 changed files with 361 additions and 62 deletions
|
@ -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>
|
||||
|
|
|
@ -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() + '/';
|
||||
|
|
|
@ -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
|
||||
|
|
44
src/gui/tray/NCProgressBar.qml
Normal file
44
src/gui/tray/NCProgressBar.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
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"));
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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--;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ public:
|
|||
private:
|
||||
QJsonArray _activityData;
|
||||
QVariantMap _metaSuccess;
|
||||
quint32 _numItemsToInsert = 30;
|
||||
quint32 _numItemsToInsert = 10;
|
||||
int _startingId = 90000;
|
||||
|
||||
static FakeRemoteActivityStorage *_instance;
|
||||
|
|
|
@ -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,40 +165,75 @@ 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,
|
||||
// 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) {
|
||||
|
||||
while (activity._type != previousType) {
|
||||
++currentTypeSection;
|
||||
previousType = activityDefaultTypeOrder[currentTypeSection];
|
||||
}
|
||||
|
||||
QCOMPARE(activity._type, activityDefaultTypeOrder[currentTypeSection]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(TestSortedActivityListModel)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
1
theme/black/expand-less-black.svg
Normal file
1
theme/black/expand-less-black.svg
Normal 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 |
Loading…
Reference in a new issue