Skip to content

Commit

Permalink
Merge pull request #2505 from nextcloud/status-feature
Browse files Browse the repository at this point in the history
Status feature.
  • Loading branch information
Camila authored Mar 24, 2021
2 parents c29c011 + 9219926 commit e8669ad
Show file tree
Hide file tree
Showing 23 changed files with 460 additions and 34 deletions.
1 change: 1 addition & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ set(client_SRCS
systray.cpp
thumbnailjob.cpp
userinfo.cpp
userstatus.cpp
accountstate.cpp
addcertificatedialog.cpp
authenticationdialog.cpp
Expand Down
33 changes: 33 additions & 0 deletions src/gui/accountstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ AccountState::AccountState(AccountPtr account)
, _waitingForNewCredentials(false)
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
, _remoteWipe(new RemoteWipe(_account))
, _userStatus(new UserStatus(this))
, _isDesktopNotificationsAllowed(true)
{
qRegisterMetaType<AccountState *>("AccountState*");

Expand Down Expand Up @@ -125,6 +127,21 @@ void AccountState::setState(State state)
emit stateChanged(_state);
}

UserStatus::Status AccountState::status() const
{
return _userStatus->status();
}

QString AccountState::statusMessage() const
{
return _userStatus->message();
}

QUrl AccountState::statusIcon() const
{
return _userStatus->icon();
}

QString AccountState::stateString(State state)
{
switch (state) {
Expand Down Expand Up @@ -205,6 +222,16 @@ void AccountState::setNavigationAppsEtagResponseHeader(const QByteArray &value)
_navigationAppsEtagResponseHeader = value;
}

bool AccountState::isDesktopNotificationsAllowed() const
{
return _isDesktopNotificationsAllowed;
}

void AccountState::setDesktopNotificationsAllowed(const bool isAllowed)
{
_isDesktopNotificationsAllowed = isAllowed;
}

void AccountState::checkConnectivity()
{
if (isSignedOut() || _waitingForNewCredentials) {
Expand Down Expand Up @@ -422,6 +449,12 @@ void AccountState::fetchNavigationApps(){
job->getNavigationApps();
}

void AccountState::fetchUserStatus()
{
connect(_userStatus, &UserStatus::fetchUserStatusFinished, this, &AccountState::statusChanged);
_userStatus->fetchUserStatus(_account);
}

void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
if(statusCode == 200){
qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
Expand Down
30 changes: 30 additions & 0 deletions src/gui/accountstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <QPointer>
#include "connectionvalidator.h"
#include "creds/abstractcredentials.h"
#include "userstatus.h"
#include <memory>

class QSettings;
Expand Down Expand Up @@ -161,6 +162,32 @@ class AccountState : public QObject, public QSharedData
///Asks for user credentials
void handleInvalidCredentials();

/** Returns the user status (Online, Dnd, Away, Offline, Invisible)
* https://gist.github.com/georgehrke/55a0412007f13be1551d1f9436a39675
*/
UserStatus::Status status() const;

/** Returns the user status Message (emoji + text)
*/
QString statusMessage() const;

/** Returns the user status icon url
*/
QUrl statusIcon() const;

/** Returns the notifications status retrieved by the notificatons endpoint
* https://github.com/nextcloud/desktop/issues/2318#issuecomment-680698429
*/
bool isDesktopNotificationsAllowed() const;

/** Set desktop notifications status retrieved by the notificatons endpoint
*/
void setDesktopNotificationsAllowed(const bool isAllowed);

/** Fetch the user status (status, icon, message)
*/
void fetchUserStatus();

public slots:
/// Triggers a ping to the server to update state and
/// connection status and errors.
Expand All @@ -174,6 +201,7 @@ public slots:
void stateChanged(State state);
void isConnectedChanged();
void hasFetchedNavigationApps();
void statusChanged();

protected Q_SLOTS:
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
Expand Down Expand Up @@ -223,6 +251,8 @@ protected Q_SLOTS:
*/
AccountAppList _apps;

UserStatus *_userStatus;
bool _isDesktopNotificationsAllowed;
};

class AccountApp : public QObject
Expand Down
10 changes: 10 additions & 0 deletions src/gui/tray/NotificationHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ void ServerNotificationHandler::slotFetchNotifications()
this, &ServerNotificationHandler::slotNotificationsReceived);
QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived,
this, &ServerNotificationHandler::slotEtagResponseHeaderReceived);
QObject::connect(_notificationJob.data(), &JsonApiJob::allowDesktopNotificationsChanged,
this, &ServerNotificationHandler::slotAllowDesktopNotificationsChanged);
_notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue<AccountState *>(_accountState));
_notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader());
_notificationJob->start();
Expand All @@ -62,6 +64,14 @@ void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray
}
}

void ServerNotificationHandler::slotAllowDesktopNotificationsChanged(const bool isAllowed)
{
auto *account = qvariant_cast<AccountState *>(sender()->property(propertyAccountStateC));
if (account != nullptr) {
account->setDesktopNotificationsAllowed(isAllowed);
}
}

void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData)
{
iconCache.insert(sender()->property("activityId").toInt(),iconData);
Expand Down
1 change: 1 addition & 0 deletions src/gui/tray/NotificationHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ private slots:
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
void slotIconDownloaded(QByteArray iconData);
void slotAllowDesktopNotificationsChanged(const bool isAllowed);

private:
QPointer<JsonApiJob> _notificationJob;
Expand Down
35 changes: 16 additions & 19 deletions src/gui/tray/UserLine.qml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ MenuItem {
anchors.fill: parent
hoverEnabled: true
onContainsMouseChanged: {
accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
accountStatusIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white")
}
onClicked: {
if (!isCurrentUser) {
Expand Down Expand Up @@ -71,27 +71,25 @@ MenuItem {
Layout.preferredHeight: (userLineLayout.height -16)
Layout.preferredWidth: (userLineLayout.height -16)
Rectangle {
id: accountStateIndicatorBackground
width: accountStateIndicator.sourceSize.width + 2
id: accountStatusIndicatorBackground
width: accountStatusIndicator.sourceSize.width + 2
height: width
anchors.bottom: accountAvatar.bottom
anchors.right: accountAvatar.right
color: "white"
radius: width*0.5
}
Image {
id: accountStateIndicator
source: model.isConnected
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
id: accountStatusIndicator
source: model.statusIcon
cache: false
x: accountStateIndicatorBackground.x + 1
y: accountStateIndicatorBackground.y + 1
x: accountStatusIndicatorBackground.x + 1
y: accountStatusIndicatorBackground.y + 1
sourceSize.width: Style.accountAvatarStateIndicatorSize
sourceSize.height: Style.accountAvatarStateIndicatorSize

Accessible.role: Accessible.Indicator
Accessible.name: model.isConnected ? qsTr("Account connected") : qsTr("Account not connected")
Accessible.name: model.isStatusOnline ? qsTr("Current user status is online") : qsTr("Current user status is do not disturb")
}
}

Expand All @@ -109,6 +107,14 @@ MenuItem {
font.pixelSize: 12
font.bold: true
}
Label {
id: userStatusMessage
width: 128
text: statusMessage
elide: Text.ElideRight
color: "black"
font.pixelSize: 10
}
Label {
id: accountServer
width: 128
Expand Down Expand Up @@ -223,13 +229,4 @@ MenuItem {
}
}
}

Connections {
target: UserModel
onRefreshCurrentUserGui: {
accountStateIndicator.source = model.isConnected
? Style.stateOnlineImageSource
: Style.stateOfflineImageSource
}
}
} // MenuItem userLine
54 changes: 53 additions & 1 deletion src/gui/tray/UserModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
connect(this, &User::guiLog, Logger::instance(), &Logger::guiLog);

connect(_account->account().data(), &Account::accountChangedAvatar, this, &User::avatarChanged);
connect(_account.data(), &AccountState::statusChanged, this, &User::statusChanged);

connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
}
Expand Down Expand Up @@ -87,7 +88,7 @@ void User::slotBuildNotificationDisplay(const ActivityList &list)

// Assemble a tray notification for the NEW notification
ConfigFile cfg;
if (cfg.optionalServerNotifications()) {
if (cfg.optionalServerNotifications() && isDesktopNotificationsAllowed()) {
if (AccountManager::instance()->accounts().count() == 1) {
emit guiLog(activity._subject, "");
} else {
Expand Down Expand Up @@ -185,6 +186,8 @@ void User::slotRefreshImmediately() {

void User::slotRefresh()
{
slotRefreshUserStatus();

if (checkPushNotificationsAreReady()) {
// we are relying on WebSocket push notifications - ignore refresh attempts from UI
_timeSinceLastCheck[_account.data()].invalidate();
Expand Down Expand Up @@ -216,6 +219,13 @@ void User::slotRefreshActivities()
_activityModel->slotRefreshActivity();
}

void User::slotRefreshUserStatus() {
// TODO: check for _account->account()->capabilities().userStatus()
if (_account.data() && _account.data()->isConnected()) {
_account.data()->fetchUserStatus();
}
}

void User::slotRefreshNotifications()
{
// start a server notification handler if no notification requests
Expand Down Expand Up @@ -557,6 +567,21 @@ QString User::server(bool shortened) const
return serverUrl;
}

UserStatus::Status User::status() const
{
return _account->status();
}

QString User::statusMessage() const
{
return _account->statusMessage();
}

QUrl User::statusIcon() const
{
return _account->statusIcon();
}

QImage User::avatar() const
{
return AvatarJob::makeCircularAvatar(_account->account()->avatar());
Expand Down Expand Up @@ -606,6 +631,12 @@ bool User::isConnected() const
return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected);
}


bool User::isDesktopNotificationsAllowed() const
{
return _account.data()->isDesktopNotificationsAllowed();
}

void User::removeAccount() const
{
AccountManager::instance()->deleteAccount(_account.data());
Expand Down Expand Up @@ -667,6 +698,16 @@ Q_INVOKABLE bool UserModel::isUserConnected(const int &id)
return _users[id]->isConnected();
}

Q_INVOKABLE QUrl UserModel::statusIcon(const int &id)
{
if (id < 0 || id >= _users.size()) {
return {};
}

return _users[id]->statusIcon();
}


QImage UserModel::avatarById(const int &id)
{
if (id < 0 || id >= _users.size())
Expand Down Expand Up @@ -703,6 +744,11 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent)
emit dataChanged(index(row, 0), index(row, 0), {UserModel::AvatarRole});
});

connect(u, &User::statusChanged, this, [this, row] {
emit dataChanged(index(row, 0), index(row, 0), {UserModel::StatusIconRole,
UserModel::StatusMessageRole});
});

_users << u;
if (isCurrent) {
_currentUserId = _users.indexOf(_users.last());
Expand Down Expand Up @@ -841,6 +887,10 @@ QVariant UserModel::data(const QModelIndex &index, int role) const
return _users[index.row()]->name();
} else if (role == ServerRole) {
return _users[index.row()]->server();
} else if (role == StatusIconRole) {
return _users[index.row()]->statusIcon();
} else if (role == StatusMessageRole) {
return _users[index.row()]->statusMessage();
} else if (role == AvatarRole) {
return _users[index.row()]->avatarUrl();
} else if (role == IsCurrentUserRole) {
Expand All @@ -858,6 +908,8 @@ QHash<int, QByteArray> UserModel::roleNames() const
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[ServerRole] = "server";
roles[StatusIconRole] = "statusIcon";
roles[StatusMessageRole] = "statusMessage";
roles[AvatarRole] = "avatar";
roles[IsCurrentUserRole] = "isCurrentUser";
roles[IsConnectedRole] = "isConnected";
Expand Down
Loading

0 comments on commit e8669ad

Please sign in to comment.