Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions src/gui/tray/UserLine.qml
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,45 @@ AbstractButton {
id: userLineLayout
spacing: Style.userLineSpacing

Image {
id: accountAvatar
Layout.leftMargin: Style.accountIconsMenuMargin
verticalAlignment: Qt.AlignCenter
cache: false
source: model.avatar !== "" ? model.avatar : Style.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
Layout.preferredHeight: Style.accountAvatarSize
Layout.preferredWidth: Style.accountAvatarSize

Rectangle {
id: accountStatusIndicatorBackground
visible: model.isConnected && model.serverHasUserStatus
width: accountStatusIndicator.sourceSize.width + Style.trayFolderStatusIndicatorSizeOffset
height: width
Image {
id: accountAvatar
Layout.leftMargin: Style.accountIconsMenuMargin
verticalAlignment: Qt.AlignCenter
cache: false
source: model.avatar !== "" ? model.avatar : Style.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
Layout.preferredHeight: Style.accountAvatarSize
Layout.preferredWidth: Style.accountAvatarSize

Rectangle {
id: syncStatusIndicatorBackground
visible: !model.syncStatusOk
width: syncStatusIndicator.sourceSize.width + Style.trayFolderStatusIndicatorSizeOffset
height: width
color: "white"
anchors.top: accountAvatar.top
anchors.left: accountAvatar.left
radius: width * Style.trayFolderStatusIndicatorRadiusFactor
}

Image {
id: syncStatusIndicator
visible: !model.syncStatusOk
source: model.syncStatusIcon
cache: false
x: syncStatusIndicatorBackground.x + Style.trayFolderStatusIndicatorSizeOffset / 2
y: syncStatusIndicatorBackground.y + Style.trayFolderStatusIndicatorSizeOffset / 2
sourceSize.width: Style.accountAvatarStateIndicatorSize
sourceSize.height: Style.accountAvatarStateIndicatorSize

Accessible.role: Accessible.Indicator
Accessible.name: qsTr("Account sync status requires attention")
}

Rectangle {
id: accountStatusIndicatorBackground
visible: model.isConnected && model.serverHasUserStatus
width: accountStatusIndicator.sourceSize.width + Style.trayFolderStatusIndicatorSizeOffset
height: width
color: "white"
anchors.bottom: accountAvatar.bottom
anchors.right: accountAvatar.right
Expand Down
126 changes: 125 additions & 1 deletion src/gui/tray/usermodel.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
Expand All @@ -17,6 +17,7 @@
#include "notificationconfirmjob.h"
#include "logger.h"
#include "guiutility.h"
#include "syncresult.h"
#include "syncfileitem.h"
#include "systray.h"
#include "tray/activitylistmodel.h"
Expand All @@ -33,6 +34,7 @@
#include <QPainter>
#include <QPushButton>
#include <QDateTime>
#include <theme.h>

// time span in milliseconds which has to be between two
// refreshes of the notifications
Expand All @@ -41,6 +43,82 @@
namespace {
constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;

struct SyncStatusInfo {

Check warning on line 47 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:47:8 [cppcoreguidelines-pro-type-member-init]

constructor does not initialize these fields: icon
QUrl icon;
bool ok = true;
};

OCC::SyncResult::Status determineSyncStatus(const OCC::SyncResult &syncResult)

Check warning on line 52 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:52:25 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
const auto status = syncResult.status();

if (status == OCC::SyncResult::Success || status == OCC::SyncResult::Problem) {
if (syncResult.hasUnresolvedConflicts()) {
return OCC::SyncResult::Problem;
}
return OCC::SyncResult::Success;
} else if (status == OCC::SyncResult::SyncPrepare || status == OCC::SyncResult::Undefined) {
return OCC::SyncResult::SyncRunning;
}
return status;
}

SyncStatusInfo syncStatusForAccount(const OCC::AccountStatePtr &accountState)

Check warning on line 67 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:67:16 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
if (!accountState || !accountState->isConnected()) {
return {OCC::Theme::instance()->offline(), false};
}

bool hasError = false;
bool hasWarning = false;
bool hasPaused = false;
bool hasSyncing = false;
for (const auto &folder : OCC::FolderMan::instance()->map()) {
if (!folder || folder->accountState() != accountState.data()) {
continue;
}

const auto state = determineSyncStatus(folder->syncResult());

switch (state) {
case OCC::SyncResult::Error:
case OCC::SyncResult::SetupError:
hasError = true;
break;
case OCC::SyncResult::Problem:
case OCC::SyncResult::Undefined:
hasWarning = true;
break;
case OCC::SyncResult::Paused:
case OCC::SyncResult::SyncAbortRequested:
hasPaused = true;
break;
case OCC::SyncResult::SyncRunning:
case OCC::SyncResult::NotYetStarted:
hasSyncing = true;
break;
case OCC::SyncResult::Success:
case OCC::SyncResult::SyncPrepare:
break;
}
}

if (hasError) {
return {OCC::Theme::instance()->error(), false};
}
if (hasWarning) {
return {OCC::Theme::instance()->warning(), false};
}
if (hasPaused) {
return {OCC::Theme::instance()->pause(), false};
}
if (hasSyncing) {
return {OCC::Theme::instance()->sync(), false};
}

return {OCC::Theme::instance()->ok(), true};
}
}

namespace OCC {
Expand Down Expand Up @@ -122,6 +200,18 @@
showDesktopNotification(certificateNeedMigration);
}
});

const auto folderMan = FolderMan::instance();
connect(folderMan, &FolderMan::folderSyncStateChange, this, [this](const Folder *folder) {
if (!folder || folder->accountState() == _account.data()) {
updateSyncStatus();
}
});
connect(folderMan, &FolderMan::folderListChanged, this, [this](const Folder::Map &) {
updateSyncStatus();
});
connect(_account.data(), &AccountState::isConnectedChanged, this, &User::updateSyncStatus);
updateSyncStatus();
}

void User::checkNotifiedNotifications()
Expand Down Expand Up @@ -1041,6 +1131,28 @@
return _account->account()->userStatusConnector()->userStatus().icon();
}

QUrl User::syncStatusIcon() const

Check warning on line 1134 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1134:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
return _syncStatusIcon;
}

bool User::syncStatusOk() const

Check warning on line 1139 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1139:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
return _syncStatusOk;
}

void User::updateSyncStatus()

Check warning on line 1144 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1144:12 [readability-convert-member-functions-to-static]

method 'updateSyncStatus' can be made static
{
const auto info = syncStatusForAccount(_account);
if (_syncStatusIcon == info.icon && _syncStatusOk == info.ok) {
return;
}

_syncStatusIcon = info.icon;
_syncStatusOk = info.ok;
emit syncStatusChanged();

Check warning on line 1153 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1153:10 [modernize-use-trailing-return-type]

use a trailing return type for this function
}

bool User::serverHasUserStatus() const
{
return _account->account()->capabilities().userStatus();
Expand Down Expand Up @@ -1445,6 +1557,11 @@
emit dataChanged(index(row, 0), index(row, 0), { UserModel::IsConnectedRole });
});

connect(u, &User::syncStatusChanged, this, [this, row] {
emit dataChanged(index(row, 0), index(row, 0), { UserModel::SyncStatusIconRole,

Check warning on line 1561 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1561:18 [cppcoreguidelines-init-variables]

variable 'dataChanged' is not initialized
UserModel::SyncStatusOkRole });
});

_users << u;
if (isCurrent || (_currentUserId < 0 && !_init)) {
setCurrentUserId(_users.size() - 1);
Expand Down Expand Up @@ -1620,7 +1737,7 @@
auto result = QVariant{};
switch (static_cast<UserRoles>(role))
{
case NameRole:

Check warning on line 1740 in src/gui/tray/usermodel.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.cpp:1740:5 [bugprone-branch-clone]

switch has 16 consecutive identical branches
result = _users[index.row()]->name();
break;
case ServerRole:
Expand Down Expand Up @@ -1662,6 +1779,12 @@
case RemoveAccountTextRole:
result = _users[index.row()]->isPublicShareLink() ? tr("Leave share") : tr("Remove account");
break;
case SyncStatusIconRole:
result = _users[index.row()]->syncStatusIcon();
break;
case SyncStatusOkRole:
result = _users[index.row()]->syncStatusOk();
break;
}

return result;
Expand All @@ -1684,6 +1807,8 @@
roles[IdRole] = "id";
roles[CanLogoutRole] = "canLogout";
roles[RemoveAccountTextRole] = "removeAccountText";
roles[SyncStatusIconRole] = "syncStatusIcon";
roles[SyncStatusOkRole] = "syncStatusOk";
return roles;
}

Expand Down Expand Up @@ -1904,4 +2029,3 @@
return roles;
}
}

10 changes: 10 additions & 0 deletions src/gui/tray/usermodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#ifndef USERMODEL_H
#define USERMODEL_H

#include <QAbstractListModel>

Check failure on line 9 in src/gui/tray/usermodel.h

View workflow job for this annotation

GitHub Actions / build

src/gui/tray/usermodel.h:9:10 [clang-diagnostic-error]

'QAbstractListModel' file not found
#include <QImage>
#include <QDateTime>
#include <QStringList>
Expand Down Expand Up @@ -67,6 +67,8 @@
Q_PROPERTY(QString featuredAppIcon READ featuredAppIcon NOTIFY featuredAppChanged)
Q_PROPERTY(QString featuredAppAccessibleName READ featuredAppAccessibleName NOTIFY featuredAppChanged)
Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
Q_PROPERTY(QUrl syncStatusIcon READ syncStatusIcon NOTIFY syncStatusChanged)
Q_PROPERTY(bool syncStatusOk READ syncStatusOk NOTIFY syncStatusChanged)
Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged)
Q_PROPERTY(bool needsToSignTermsOfService READ needsToSignTermsOfService NOTIFY accountStateChanged)
Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT)
Expand Down Expand Up @@ -112,6 +114,8 @@
[[nodiscard]] QString statusMessage() const;
[[nodiscard]] QUrl statusIcon() const;
[[nodiscard]] QString statusEmoji() const;
[[nodiscard]] QUrl syncStatusIcon() const;
[[nodiscard]] bool syncStatusOk() const;
void processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item);
[[nodiscard]] const QVariantList &groupFolders() const;
[[nodiscard]] bool canLogout() const;
Expand All @@ -128,6 +132,7 @@
void headerColorChanged();
void headerTextColorChanged();
void accentColorChanged();
void syncStatusChanged();
void sendReplyMessage(const int activityIndex, const QString &conversationToken, const QString &message, const QString &replyTo);
void groupFoldersChanged();

Expand Down Expand Up @@ -180,6 +185,7 @@

bool isActivityOfCurrentAccount(const Folder *folder) const;
[[nodiscard]] bool isUnsolvableConflict(const SyncFileItemPtr &item) const;
void updateSyncStatus();

bool notificationAlreadyShown(const qint64 notificationId);
bool canShowNotification(const qint64 notificationId);
Expand Down Expand Up @@ -213,6 +219,8 @@
// used for quota warnings
int _lastQuotaPercent = 0;
Activity _lastQuotaActivity;
QUrl _syncStatusIcon;
bool _syncStatusOk = true;
};

class UserModel : public QAbstractListModel
Expand Down Expand Up @@ -262,6 +270,8 @@
IdRole,
CanLogoutRole,
RemoveAccountTextRole,
SyncStatusIconRole,
SyncStatusOkRole,
};

[[nodiscard]] AccountAppList appList() const;
Expand Down
Loading