mirror of
https://github.com/nextcloud/desktop.git
synced 2024-10-23 04:45:43 +03:00
Add dialog to set user status
Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
This commit is contained in:
parent
f34d663029
commit
8a8d488454
42 changed files with 4592 additions and 294 deletions
|
@ -1,5 +1,9 @@
|
|||
<RCC>
|
||||
<qresource prefix="/qml">
|
||||
<file>src/gui/UserStatusSelector.qml</file>
|
||||
<file>src/gui/UserStatusSelectorDialog.qml</file>
|
||||
<file>src/gui/EmojiPicker.qml</file>
|
||||
<file>src/gui/ErrorBox.qml</file>
|
||||
<file>src/gui/tray/Window.qml</file>
|
||||
<file>src/gui/tray/UserLine.qml</file>
|
||||
<file>src/gui/tray/HeaderButton.qml</file>
|
||||
|
|
|
@ -35,6 +35,8 @@ set(client_UI_SRCS
|
|||
addcertificatedialog.ui
|
||||
proxyauthdialog.ui
|
||||
mnemonicdialog.ui
|
||||
UserStatusSelector.qml
|
||||
UserStatusSelectorDialog.qml
|
||||
tray/ActivityActionButton.qml
|
||||
tray/ActivityItem.qml
|
||||
tray/Window.qml
|
||||
|
@ -92,7 +94,6 @@ set(client_SRCS
|
|||
systray.cpp
|
||||
thumbnailjob.cpp
|
||||
userinfo.cpp
|
||||
userstatus.cpp
|
||||
accountstate.cpp
|
||||
addcertificatedialog.cpp
|
||||
authenticationdialog.cpp
|
||||
|
@ -106,6 +107,8 @@ set(client_SRCS
|
|||
iconjob.cpp
|
||||
iconutils.cpp
|
||||
remotewipe.cpp
|
||||
userstatusselectormodel.cpp
|
||||
emojimodel.cpp
|
||||
tray/ActivityData.cpp
|
||||
tray/ActivityListModel.cpp
|
||||
tray/UserModel.cpp
|
||||
|
|
112
src/gui/EmojiPicker.qml
Normal file
112
src/gui/EmojiPicker.qml
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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 QtQuick.Layouts 1.15
|
||||
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
ColumnLayout {
|
||||
NC.EmojiModel {
|
||||
id: emojiModel
|
||||
}
|
||||
|
||||
signal chosen(string emoji)
|
||||
|
||||
spacing: 0
|
||||
|
||||
FontMetrics {
|
||||
id: metrics
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: headerLayout
|
||||
Layout.fillWidth: true
|
||||
implicitWidth: contentItem.childrenRect.width
|
||||
implicitHeight: metrics.height * 2
|
||||
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: emojiModel.emojiCategoriesModel
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: metrics.height * 2
|
||||
height: headerLayout.height
|
||||
|
||||
contentItem: Text {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: emoji
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: parent.width
|
||||
height: 2
|
||||
|
||||
visible: ListView.isCurrentItem
|
||||
|
||||
color: "grey"
|
||||
}
|
||||
|
||||
|
||||
onClicked: {
|
||||
emojiModel.setCategory(label)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: 1
|
||||
Layout.fillWidth: true
|
||||
color: "grey"
|
||||
}
|
||||
|
||||
GridView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredHeight: metrics.height * 8
|
||||
|
||||
cellWidth: metrics.height * 2
|
||||
cellHeight: metrics.height * 2
|
||||
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
clip: true
|
||||
|
||||
model: emojiModel.model
|
||||
|
||||
delegate: ItemDelegate {
|
||||
|
||||
width: metrics.height * 2
|
||||
height: metrics.height * 2
|
||||
|
||||
contentItem: Text {
|
||||
anchors.centerIn: parent
|
||||
text: modelData === undefined ? "" : modelData.unicode
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
chosen(modelData.unicode);
|
||||
emojiModel.emojiUsed(modelData);
|
||||
}
|
||||
}
|
||||
|
||||
ScrollBar.vertical: ScrollBar {}
|
||||
|
||||
}
|
||||
|
||||
}
|
26
src/gui/ErrorBox.qml
Normal file
26
src/gui/ErrorBox.qml
Normal file
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
Item {
|
||||
id: errorBox
|
||||
|
||||
property var text: ""
|
||||
|
||||
implicitHeight: errorMessage.implicitHeight + 2 * 8
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "red"
|
||||
border.color: "black"
|
||||
}
|
||||
|
||||
Text {
|
||||
id: errorMessage
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 8
|
||||
width: parent.width
|
||||
color: "white"
|
||||
wrapMode: Text.WordWrap
|
||||
text: errorBox.text
|
||||
}
|
||||
}
|
199
src/gui/UserStatusSelector.qml
Normal file
199
src/gui/UserStatusSelector.qml
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.6
|
||||
import QtQuick.Dialogs 1.3
|
||||
import QtQuick.Layouts 1.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
ColumnLayout {
|
||||
id: rootLayout
|
||||
spacing: 0
|
||||
property NC.UserStatusSelectorModel userStatusSelectorModel
|
||||
|
||||
FontMetrics {
|
||||
id: metrics
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Online status")
|
||||
}
|
||||
|
||||
GridLayout {
|
||||
Layout.margins: 8
|
||||
Layout.alignment: Qt.AlignTop
|
||||
columns: 2
|
||||
rows: 2
|
||||
columnSpacing: 8
|
||||
rowSpacing: 8
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
checked: NC.UserStatus.Online == userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.onlineIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Online")
|
||||
onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Online)
|
||||
implicitWidth: 100
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
checked: NC.UserStatus.Away == userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.awayIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Away")
|
||||
onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Away)
|
||||
implicitWidth: 100
|
||||
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
checked: NC.UserStatus.DoNotDisturb == userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.dndIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Do not disturb")
|
||||
onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.DoNotDisturb)
|
||||
implicitWidth: 100
|
||||
}
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
checked: NC.UserStatus.Invisible == userStatusSelectorModel.onlineStatus
|
||||
checkable: true
|
||||
icon.source: userStatusSelectorModel.invisibleIcon
|
||||
icon.color: "transparent"
|
||||
text: qsTr("Invisible")
|
||||
onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Invisible)
|
||||
implicitWidth: 100
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||
font.bold: true
|
||||
text: qsTr("Status message")
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 16
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
Layout.preferredWidth: userStatusMessageTextField.height // metrics.height * 2
|
||||
Layout.preferredHeight: userStatusMessageTextField.height // metrics.height * 2
|
||||
text: userStatusSelectorModel.userStatusEmoji
|
||||
onClicked: emojiDialog.open()
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: emojiDialog
|
||||
padding: 0
|
||||
margins: 0
|
||||
|
||||
anchors.centerIn: Overlay.overlay
|
||||
|
||||
EmojiPicker {
|
||||
id: emojiPicker
|
||||
|
||||
onChosen: {
|
||||
userStatusSelectorModel.userStatusEmoji = emoji
|
||||
emojiDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: userStatusMessageTextField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("What is your Status?")
|
||||
text: userStatusSelectorModel.userStatusMessage
|
||||
onEditingFinished: userStatusSelectorModel.setUserStatusMessage(text)
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: userStatusSelectorModel.predefinedStatusesCount
|
||||
|
||||
Button {
|
||||
id: control
|
||||
Layout.fillWidth: true
|
||||
flat: !hovered
|
||||
hoverEnabled: true
|
||||
text: userStatusSelectorModel.predefinedStatus(index).icon + " <b>" + userStatusSelectorModel.predefinedStatus(index).message + "</b> - " + userStatusSelectorModel.predefinedStatusClearAt(index)
|
||||
onClicked: userStatusSelectorModel.setPredefinedStatus(index)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 8
|
||||
Layout.rightMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Text {
|
||||
text: qsTr("Clear status message after")
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: userStatusSelectorModel.clearAtValues
|
||||
displayText: userStatusSelectorModel.clearAt
|
||||
onActivated: userStatusSelectorModel.setClearAt(index)
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.margins: 8
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
Button {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Clear status message")
|
||||
onClicked: userStatusSelectorModel.clearUserStatus()
|
||||
}
|
||||
Button {
|
||||
highlighted: true
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Set status message")
|
||||
onClicked: userStatusSelectorModel.setUserStatus()
|
||||
}
|
||||
}
|
||||
|
||||
ErrorBox {
|
||||
Layout.margins: 8
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: userStatusSelectorModel.errorMessage != ""
|
||||
text: "<b>Error:</b> " + userStatusSelectorModel.errorMessage
|
||||
}
|
||||
}
|
29
src/gui/UserStatusSelectorDialog.qml
Normal file
29
src/gui/UserStatusSelectorDialog.qml
Normal file
|
@ -0,0 +1,29 @@
|
|||
import QtQuick.Window 2.15
|
||||
|
||||
import com.nextcloud.desktopclient 1.0 as NC
|
||||
|
||||
Window {
|
||||
id: dialog
|
||||
|
||||
property NC.UserStatusSelectorModel model: NC.UserStatusSelectorModel {
|
||||
onFinished: {
|
||||
dialog.close()
|
||||
}
|
||||
}
|
||||
|
||||
width: view.implicitWidth
|
||||
height: view.implicitHeight
|
||||
minimumWidth: view.implicitWidth
|
||||
minimumHeight: view.implicitHeight
|
||||
maximumWidth: view.implicitWidth
|
||||
maximumHeight: view.implicitHeight
|
||||
|
||||
visible: true
|
||||
|
||||
flags: Qt.Dialog
|
||||
|
||||
UserStatusSelector {
|
||||
id: view
|
||||
userStatusSelectorModel: model
|
||||
}
|
||||
}
|
|
@ -44,7 +44,6 @@ 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*");
|
||||
|
@ -127,26 +126,6 @@ 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::statusEmoji() const
|
||||
{
|
||||
return _userStatus->emoji();
|
||||
}
|
||||
|
||||
QString AccountState::stateString(State state)
|
||||
{
|
||||
switch (state) {
|
||||
|
@ -462,12 +441,6 @@ 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;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
#include <QPointer>
|
||||
#include "connectionvalidator.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "userstatus.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QSettings;
|
||||
|
@ -162,23 +162,6 @@ public:
|
|||
///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 (text)
|
||||
*/
|
||||
QString statusMessage() const;
|
||||
|
||||
/** Returns the user status icon url
|
||||
*/
|
||||
QUrl statusIcon() const;
|
||||
|
||||
/** Returns the user status emoji
|
||||
*/
|
||||
QString statusEmoji() const;
|
||||
|
||||
/** Returns the notifications status retrieved by the notificatons endpoint
|
||||
* https://github.com/nextcloud/desktop/issues/2318#issuecomment-680698429
|
||||
*/
|
||||
|
@ -188,10 +171,6 @@ public:
|
|||
*/
|
||||
void setDesktopNotificationsAllowed(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.
|
||||
|
@ -256,7 +235,6 @@ private:
|
|||
*/
|
||||
AccountAppList _apps;
|
||||
|
||||
UserStatus *_userStatus;
|
||||
bool _isDesktopNotificationsAllowed;
|
||||
};
|
||||
|
||||
|
|
1571
src/gui/emojimodel.cpp
Normal file
1571
src/gui/emojimodel.cpp
Normal file
File diff suppressed because it is too large
Load diff
137
src/gui/emojimodel.h
Normal file
137
src/gui/emojimodel.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QVariant>
|
||||
#include <QVector>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
struct Emoji
|
||||
{
|
||||
Emoji(QString u, QString s, bool isCustom = false)
|
||||
: unicode(std::move(std::move(u)))
|
||||
, shortname(std::move(std::move(s)))
|
||||
, isCustom(isCustom)
|
||||
{
|
||||
}
|
||||
Emoji() = default;
|
||||
|
||||
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
|
||||
{
|
||||
arch << object.unicode;
|
||||
arch << object.shortname;
|
||||
return arch;
|
||||
}
|
||||
|
||||
friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
|
||||
{
|
||||
arch >> object.unicode;
|
||||
arch >> object.shortname;
|
||||
object.isCustom = object.unicode.startsWith("image://");
|
||||
return arch;
|
||||
}
|
||||
|
||||
QString unicode;
|
||||
QString shortname;
|
||||
bool isCustom = false;
|
||||
|
||||
Q_GADGET
|
||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||
Q_PROPERTY(QString shortname MEMBER shortname)
|
||||
Q_PROPERTY(bool isCustom MEMBER isCustom)
|
||||
};
|
||||
|
||||
class EmojiCategoriesModel : public QAbstractListModel
|
||||
{
|
||||
public:
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
enum Roles {
|
||||
EmojiRole = 0,
|
||||
LabelRole
|
||||
};
|
||||
|
||||
struct Category
|
||||
{
|
||||
QString emoji;
|
||||
QString label;
|
||||
};
|
||||
|
||||
static const std::vector<Category> categories;
|
||||
};
|
||||
|
||||
class EmojiModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QVariantList model READ model NOTIFY modelChanged)
|
||||
Q_PROPERTY(QAbstractListModel *emojiCategoriesModel READ emojiCategoriesModel CONSTANT)
|
||||
|
||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||
|
||||
Q_PROPERTY(QVariantList people MEMBER people CONSTANT)
|
||||
Q_PROPERTY(QVariantList nature MEMBER nature CONSTANT)
|
||||
Q_PROPERTY(QVariantList food MEMBER food CONSTANT)
|
||||
Q_PROPERTY(QVariantList activity MEMBER activity CONSTANT)
|
||||
Q_PROPERTY(QVariantList travel MEMBER travel CONSTANT)
|
||||
Q_PROPERTY(QVariantList objects MEMBER objects CONSTANT)
|
||||
Q_PROPERTY(QVariantList symbols MEMBER symbols CONSTANT)
|
||||
Q_PROPERTY(QVariantList flags MEMBER flags CONSTANT)
|
||||
|
||||
public:
|
||||
explicit EmojiModel(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Q_INVOKABLE QVariantList history() const;
|
||||
Q_INVOKABLE void setCategory(const QString &category);
|
||||
Q_INVOKABLE void emojiUsed(const QVariant &modelData);
|
||||
|
||||
QVariantList model() const;
|
||||
QAbstractListModel *emojiCategoriesModel();
|
||||
|
||||
signals:
|
||||
void historyChanged();
|
||||
void modelChanged();
|
||||
|
||||
private:
|
||||
static const QVariantList people;
|
||||
static const QVariantList nature;
|
||||
static const QVariantList food;
|
||||
static const QVariantList activity;
|
||||
static const QVariantList travel;
|
||||
static const QVariantList objects;
|
||||
static const QVariantList symbols;
|
||||
static const QVariantList flags;
|
||||
|
||||
QSettings _settings;
|
||||
QString _category = "history";
|
||||
|
||||
EmojiCategoriesModel _emojiCategoriesModel;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(OCC::Emoji)
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <cmath>
|
||||
#include <csignal>
|
||||
#include <qqml.h>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
#include <sys/time.h>
|
||||
|
@ -26,6 +27,8 @@
|
|||
#include "theme.h"
|
||||
#include "common/utility.h"
|
||||
#include "cocoainitializer.h"
|
||||
#include "userstatusselectormodel.h"
|
||||
#include "emojimodel.h"
|
||||
|
||||
#if defined(BUILD_UPDATER)
|
||||
#include "updater/updater.h"
|
||||
|
@ -54,6 +57,15 @@ int main(int argc, char **argv)
|
|||
Q_INIT_RESOURCE(resources);
|
||||
Q_INIT_RESOURCE(theme);
|
||||
|
||||
qmlRegisterType<EmojiModel>("com.nextcloud.desktopclient", 1, 0, "EmojiModel");
|
||||
qRegisterMetaTypeStreamOperators<Emoji>();
|
||||
qmlRegisterType<UserStatusSelectorModel>("com.nextcloud.desktopclient", 1, 0,
|
||||
"UserStatusSelectorModel");
|
||||
qmlRegisterUncreatableType<OCC::UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus",
|
||||
"Access to Status enum");
|
||||
qRegisterMetaType<OCC::UserStatus>("UserStatus");
|
||||
|
||||
|
||||
// Work around a bug in KDE's qqc2-desktop-style which breaks
|
||||
// buttons with icons not based on a name, by forcing a style name
|
||||
// the platformtheme plugin won't try to force qqc2-desktops-style
|
||||
|
|
|
@ -617,7 +617,7 @@ void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
|
|||
params.addQueryItem("path", fileData.serverRelativePath);
|
||||
params.addQueryItem("editorId", editor->id());
|
||||
job->addQueryParams(params);
|
||||
job->usePOST();
|
||||
job->setVerb(JsonApiJob::Verb::Post);
|
||||
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
|
||||
auto data = json.object().value("ocs").toObject().value("data").toObject();
|
||||
|
|
|
@ -14,6 +14,11 @@ MenuItem {
|
|||
Accessible.role: Accessible.MenuItem
|
||||
Accessible.name: qsTr("Account entry")
|
||||
|
||||
property variant dialog;
|
||||
property variant comp;
|
||||
|
||||
signal showUserStatusSelectorDialog(int id)
|
||||
|
||||
RowLayout {
|
||||
id: userLineLayout
|
||||
spacing: 0
|
||||
|
@ -188,6 +193,29 @@ MenuItem {
|
|||
radius: 2
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: model.isConnected && model.serverHasUserStatus
|
||||
height: visible ? implicitHeight : 0
|
||||
|
||||
text: qsTr("Set status")
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
showUserStatusSelectorDialog(index)
|
||||
accountMenu.close()
|
||||
}
|
||||
|
||||
background: Item {
|
||||
height: parent.height
|
||||
width: parent.menu.width
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
color: parent.parent.hovered ? Style.lightHover : "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: model.isConnected ? qsTr("Log out") : qsTr("Log in")
|
||||
font.pixelSize: Style.topLinePixelSize
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "accountmanager.h"
|
||||
#include "owncloudgui.h"
|
||||
#include <pushnotifications.h>
|
||||
#include "userstatusselectormodel.h"
|
||||
#include "syncengine.h"
|
||||
#include "ocsjob.h"
|
||||
#include "configfile.h"
|
||||
|
@ -11,7 +12,9 @@
|
|||
#include "logger.h"
|
||||
#include "guiutility.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "tray/ActivityListModel.h"
|
||||
#include "tray/NotificationCache.h"
|
||||
#include "userstatusconnector.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QIcon>
|
||||
|
@ -25,8 +28,8 @@
|
|||
#define NOTIFICATION_REQUEST_FREE_PERIOD 15000
|
||||
|
||||
namespace {
|
||||
constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
|
||||
constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
|
||||
constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
|
||||
constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
@ -65,7 +68,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(_account->account().data(), &Account::userStatusChanged, this, &User::statusChanged);
|
||||
connect(_account.data(), &AccountState::desktopNotificationsAllowedChanged, this, &User::desktopNotificationsAllowedChanged);
|
||||
|
||||
connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
|
||||
|
@ -236,10 +239,10 @@ void User::slotRefreshActivities()
|
|||
_activityModel->slotRefreshActivity();
|
||||
}
|
||||
|
||||
void User::slotRefreshUserStatus()
|
||||
void User::slotRefreshUserStatus()
|
||||
{
|
||||
if (_account.data() && _account.data()->isConnected()) {
|
||||
_account.data()->fetchUserStatus();
|
||||
_account->account()->userStatusConnector()->fetchUserStatus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,29 +624,29 @@ QString User::server(bool shortened) const
|
|||
return serverUrl;
|
||||
}
|
||||
|
||||
UserStatus::Status User::status() const
|
||||
UserStatus::OnlineStatus User::status() const
|
||||
{
|
||||
return _account->status();
|
||||
return _account->account()->userStatusConnector()->userStatus().state();
|
||||
}
|
||||
|
||||
QString User::statusMessage() const
|
||||
{
|
||||
return _account->statusMessage();
|
||||
return _account->account()->userStatusConnector()->userStatus().message();
|
||||
}
|
||||
|
||||
QUrl User::statusIcon() const
|
||||
{
|
||||
return _account->statusIcon();
|
||||
return _account->account()->userStatusConnector()->userStatus().stateIcon();
|
||||
}
|
||||
|
||||
QString User::statusEmoji() const
|
||||
{
|
||||
return _account->statusEmoji();
|
||||
return _account->account()->userStatusConnector()->userStatus().icon();
|
||||
}
|
||||
|
||||
bool User::serverHasUserStatus() const
|
||||
{
|
||||
return _account->account()->capabilities().userStatus();
|
||||
return _account->account()->capabilities().userStatusNotification();
|
||||
}
|
||||
|
||||
QImage User::avatar() const
|
||||
|
@ -921,6 +924,15 @@ Q_INVOKABLE void UserModel::removeAccount(const int &id)
|
|||
endRemoveRows();
|
||||
}
|
||||
|
||||
std::shared_ptr<OCC::UserStatusConnector> UserModel::userStatusConnector(int id)
|
||||
{
|
||||
if (id < 0 || id >= _users.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return _users[id]->account()->userStatusConnector();
|
||||
}
|
||||
|
||||
int UserModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "accountmanager.h"
|
||||
#include "folderman.h"
|
||||
#include "NotificationCache.h"
|
||||
#include "userstatusselectormodel.h"
|
||||
#include "userstatusconnector.h"
|
||||
#include <chrono>
|
||||
|
||||
namespace OCC {
|
||||
|
@ -55,7 +57,7 @@ public:
|
|||
void removeAccount() const;
|
||||
QString avatarUrl() const;
|
||||
bool isDesktopNotificationsAllowed() const;
|
||||
UserStatus::Status status() const;
|
||||
UserStatus::OnlineStatus status() const;
|
||||
QString statusMessage() const;
|
||||
QUrl statusIcon() const;
|
||||
QString statusEmoji() const;
|
||||
|
@ -158,6 +160,8 @@ public:
|
|||
Q_INVOKABLE void logout(const int &id);
|
||||
Q_INVOKABLE void removeAccount(const int &id);
|
||||
|
||||
Q_INVOKABLE std::shared_ptr<OCC::UserStatusConnector> userStatusConnector(int id);
|
||||
|
||||
ActivityListModel *currentActivityModel();
|
||||
|
||||
enum UserRoles {
|
||||
|
|
|
@ -140,6 +140,10 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: userStatusSelectorDialogLoader
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: accountMenu
|
||||
|
||||
|
@ -167,7 +171,14 @@ Window {
|
|||
Instantiator {
|
||||
id: userLineInstantiator
|
||||
model: UserModel
|
||||
delegate: UserLine {}
|
||||
delegate: UserLine {
|
||||
onShowUserStatusSelectorDialog: {
|
||||
userStatusSelectorDialogLoader.source = "qrc:/qml/src/gui/UserStatusSelectorDialog.qml"
|
||||
userStatusSelectorDialogLoader.item.title = qsTr("Set user status")
|
||||
userStatusSelectorDialogLoader.item.model.load(index)
|
||||
userStatusSelectorDialogLoader.item.show()
|
||||
}
|
||||
}
|
||||
onObjectAdded: accountMenu.insertItem(index, object)
|
||||
onObjectRemoved: accountMenu.removeItem(object)
|
||||
}
|
||||
|
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Camila <hello@camila.codes>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "userstatus.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "networkjobs.h"
|
||||
#include "folderman.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "theme.h"
|
||||
#include "capabilities.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcUserStatus, "nextcloud.gui.userstatus", QtInfoMsg)
|
||||
|
||||
namespace {
|
||||
UserStatus::Status stringToEnum(const QString &status)
|
||||
{
|
||||
// it needs to match the Status enum
|
||||
const QHash<QString, UserStatus::Status> preDefinedStatus{
|
||||
{"online", UserStatus::Status::Online},
|
||||
{"dnd", UserStatus::Status::DoNotDisturb},
|
||||
{"away", UserStatus::Status::Away},
|
||||
{"offline", UserStatus::Status::Offline},
|
||||
{"invisible", UserStatus::Status::Invisible}
|
||||
};
|
||||
|
||||
// api should return invisible, dnd,... toLower() it is to make sure
|
||||
// it matches _preDefinedStatus, otherwise the default is online (0)
|
||||
return preDefinedStatus.value(status.toLower(), UserStatus::Status::Online);
|
||||
}
|
||||
}
|
||||
|
||||
UserStatus::UserStatus(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void UserStatus::fetchUserStatus(AccountPtr account)
|
||||
{
|
||||
if (!account->capabilities().userStatus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_job) {
|
||||
_job->deleteLater();
|
||||
}
|
||||
|
||||
_job = new JsonApiJob(account, QStringLiteral("/ocs/v2.php/apps/user_status/api/v1/user_status"), this);
|
||||
connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchUserStatusFinished);
|
||||
_job->start();
|
||||
}
|
||||
|
||||
void UserStatus::slotFetchUserStatusFinished(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
const QJsonObject defaultValues {
|
||||
{"icon", ""},
|
||||
{"message", ""},
|
||||
{"status", "online"},
|
||||
{"messageIsPredefined", "false"},
|
||||
{"statusIsUserDefined", "false"}
|
||||
};
|
||||
|
||||
if (statusCode != 200) {
|
||||
qCInfo(lcUserStatus) << "Slot fetch UserStatus finished with status code" << statusCode;
|
||||
qCInfo(lcUserStatus) << "Using then default values as if user has not set any status" << defaultValues;
|
||||
}
|
||||
|
||||
const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject(defaultValues);
|
||||
|
||||
_emoji = retrievedData.value("icon").toString().trimmed();
|
||||
_status = stringToEnum(retrievedData.value("status").toString());
|
||||
_message = retrievedData.value("message").toString().trimmed();
|
||||
|
||||
emit fetchUserStatusFinished();
|
||||
}
|
||||
|
||||
UserStatus::Status UserStatus::status() const
|
||||
{
|
||||
return _status;
|
||||
}
|
||||
|
||||
QString UserStatus::message() const
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
|
||||
QString UserStatus::emoji() const
|
||||
{
|
||||
return _emoji;
|
||||
}
|
||||
|
||||
QUrl UserStatus::icon() const
|
||||
{
|
||||
switch (_status) {
|
||||
case Status::Away:
|
||||
return Theme::instance()->statusAwayImageSource();
|
||||
case Status::DoNotDisturb:
|
||||
return Theme::instance()->statusDoNotDisturbImageSource();
|
||||
case Status::Invisible:
|
||||
case Status::Offline:
|
||||
return Theme::instance()->statusInvisibleImageSource();
|
||||
case Status::Online:
|
||||
return Theme::instance()->statusOnlineImageSource();
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
} // namespace OCC
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Camila <hello@camila.codes>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef USERSTATUS_H
|
||||
#define USERSTATUS_H
|
||||
|
||||
#include <QPointer>
|
||||
#include "accountfwd.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class JsonApiJob;
|
||||
|
||||
class UserStatus : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UserStatus(QObject *parent = nullptr);
|
||||
enum class Status {
|
||||
Online,
|
||||
DoNotDisturb,
|
||||
Away,
|
||||
Offline,
|
||||
Invisible
|
||||
};
|
||||
Q_ENUM(Status);
|
||||
void fetchUserStatus(AccountPtr account);
|
||||
Status status() const;
|
||||
QString message() const;
|
||||
QString emoji() const;
|
||||
QUrl icon() const;
|
||||
|
||||
private slots:
|
||||
void slotFetchUserStatusFinished(const QJsonDocument &json, int statusCode);
|
||||
|
||||
signals:
|
||||
void fetchUserStatusFinished();
|
||||
|
||||
private:
|
||||
QPointer<JsonApiJob> _job; // the currently running job
|
||||
Status _status = Status::Online;
|
||||
QString _message;
|
||||
QString _emoji;
|
||||
};
|
||||
|
||||
|
||||
} // namespace OCC
|
||||
|
||||
#endif //USERSTATUS_H
|
474
src/gui/userstatusselectormodel.cpp
Normal file
474
src/gui/userstatusselectormodel.cpp
Normal file
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#include "userstatusselectormodel.h"
|
||||
#include "tray/UserModel.h"
|
||||
|
||||
#include <ocsuserstatusconnector.h>
|
||||
#include <qnamespace.h>
|
||||
#include <userstatusconnector.h>
|
||||
#include <theme.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcUserStatusDialogModel, "nextcloud.gui.userstatusdialogmodel", QtInfoMsg)
|
||||
|
||||
UserStatusSelectorModel::UserStatusSelectorModel(QObject *parent)
|
||||
: QObject(parent)
|
||||
, _dateTimeProvider(new DateTimeProvider)
|
||||
{
|
||||
_userStatus.setIcon("😀");
|
||||
}
|
||||
|
||||
UserStatusSelectorModel::UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _userStatusConnector(userStatusConnector)
|
||||
, _userStatus("no-id", "", "😀", UserStatus::OnlineStatus::Online, false, {})
|
||||
, _dateTimeProvider(new DateTimeProvider)
|
||||
{
|
||||
_userStatus.setIcon("😀");
|
||||
init();
|
||||
}
|
||||
|
||||
UserStatusSelectorModel::UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
|
||||
std::unique_ptr<DateTimeProvider> dateTimeProvider,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, _userStatusConnector(userStatusConnector)
|
||||
, _dateTimeProvider(std::move(dateTimeProvider))
|
||||
{
|
||||
_userStatus.setIcon("😀");
|
||||
init();
|
||||
}
|
||||
|
||||
UserStatusSelectorModel::UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
std::unique_ptr<DateTimeProvider> dateTimeProvider, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _userStatus(userStatus)
|
||||
, _dateTimeProvider(std::move(dateTimeProvider))
|
||||
{
|
||||
_userStatus.setIcon("😀");
|
||||
}
|
||||
|
||||
UserStatusSelectorModel::UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, _userStatus(userStatus)
|
||||
{
|
||||
_userStatus.setIcon("😀");
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::load(int id)
|
||||
{
|
||||
reset();
|
||||
_userStatusConnector = UserModel::instance()->userStatusConnector(id);
|
||||
init();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::reset()
|
||||
{
|
||||
if (_userStatusConnector) {
|
||||
disconnect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this,
|
||||
&UserStatusSelectorModel::onUserStatusFetched);
|
||||
disconnect(_userStatusConnector.get(), &UserStatusConnector::predefinedStatusesFetched, this,
|
||||
&UserStatusSelectorModel::onPredefinedStatusesFetched);
|
||||
disconnect(_userStatusConnector.get(), &UserStatusConnector::error, this,
|
||||
&UserStatusSelectorModel::onError);
|
||||
disconnect(_userStatusConnector.get(), &UserStatusConnector::userStatusSet, this,
|
||||
&UserStatusSelectorModel::onUserStatusSet);
|
||||
disconnect(_userStatusConnector.get(), &UserStatusConnector::messageCleared, this,
|
||||
&UserStatusSelectorModel::onMessageCleared);
|
||||
}
|
||||
_userStatusConnector = nullptr;
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::init()
|
||||
{
|
||||
if (!_userStatusConnector) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this,
|
||||
&UserStatusSelectorModel::onUserStatusFetched);
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::predefinedStatusesFetched, this,
|
||||
&UserStatusSelectorModel::onPredefinedStatusesFetched);
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::error, this,
|
||||
&UserStatusSelectorModel::onError);
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::userStatusSet, this,
|
||||
&UserStatusSelectorModel::onUserStatusSet);
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::messageCleared, this,
|
||||
&UserStatusSelectorModel::onMessageCleared);
|
||||
|
||||
_userStatusConnector->fetchUserStatus();
|
||||
_userStatusConnector->fetchPredefinedStatuses();
|
||||
}
|
||||
|
||||
UserStatusSelectorModel::~UserStatusSelectorModel()
|
||||
{
|
||||
qCDebug(lcUserStatusDialogModel) << "Destroyed";
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::onUserStatusSet()
|
||||
{
|
||||
qCDebug(lcUserStatusDialogModel) << "Emit finished";
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::onMessageCleared()
|
||||
{
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::onError(UserStatusConnector::Error error)
|
||||
{
|
||||
qCWarning(lcUserStatusDialogModel) << "Error:" << error;
|
||||
|
||||
switch (error) {
|
||||
case UserStatusConnector::Error::CouldNotFetchPredefinedUserStatuses:
|
||||
setError(tr("Could not fetch predefined statuses. Make sure you are connected to the server."));
|
||||
return;
|
||||
|
||||
case UserStatusConnector::Error::CouldNotFetchUserStatus:
|
||||
setError(tr("Could not fetch user status. Make sure you are connected to the server."));
|
||||
return;
|
||||
|
||||
case UserStatusConnector::Error::UserStatusNotSupported:
|
||||
setError(tr("User status feature is not supported. You will not be able to set your user status."));
|
||||
return;
|
||||
|
||||
case UserStatusConnector::Error::EmojisNotSupported:
|
||||
setError(tr("Emojis feature is not supported. Some user status functionality may not work."));
|
||||
return;
|
||||
|
||||
case UserStatusConnector::Error::CouldNotSetUserStatus:
|
||||
setError(tr("Could not set user status. Make sure you are connected to the server."));
|
||||
return;
|
||||
|
||||
case UserStatusConnector::Error::CouldNotClearMessage:
|
||||
setError(tr("Could not clear user status message. Make sure you are connected to the server."));
|
||||
return;
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setError(const QString &reason)
|
||||
{
|
||||
_errorMessage = reason;
|
||||
emit errorMessageChanged();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::clearError()
|
||||
{
|
||||
setError("");
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setOnlineStatus(UserStatus::OnlineStatus status)
|
||||
{
|
||||
if (status == _userStatus.state()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_userStatus.setState(status);
|
||||
emit onlineStatusChanged();
|
||||
}
|
||||
|
||||
QUrl UserStatusSelectorModel::onlineIcon() const
|
||||
{
|
||||
return Theme::instance()->statusOnlineImageSource();
|
||||
}
|
||||
|
||||
QUrl UserStatusSelectorModel::awayIcon() const
|
||||
{
|
||||
return Theme::instance()->statusAwayImageSource();
|
||||
}
|
||||
QUrl UserStatusSelectorModel::dndIcon() const
|
||||
{
|
||||
return Theme::instance()->statusDoNotDisturbImageSource();
|
||||
}
|
||||
QUrl UserStatusSelectorModel::invisibleIcon() const
|
||||
{
|
||||
return Theme::instance()->statusInvisibleImageSource();
|
||||
}
|
||||
|
||||
UserStatus::OnlineStatus UserStatusSelectorModel::onlineStatus() const
|
||||
{
|
||||
return _userStatus.state();
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::userStatusMessage() const
|
||||
{
|
||||
return _userStatus.message();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setUserStatusMessage(const QString &message)
|
||||
{
|
||||
_userStatus.setMessage(message);
|
||||
_userStatus.setMessagePredefined(false);
|
||||
emit userStatusChanged();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setUserStatusEmoji(const QString &emoji)
|
||||
{
|
||||
_userStatus.setIcon(emoji);
|
||||
_userStatus.setMessagePredefined(false);
|
||||
emit userStatusChanged();
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::userStatusEmoji() const
|
||||
{
|
||||
return _userStatus.icon();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::onUserStatusFetched(const UserStatus &userStatus)
|
||||
{
|
||||
if (userStatus.state() != UserStatus::OnlineStatus::Offline) {
|
||||
_userStatus.setState(userStatus.state());
|
||||
}
|
||||
_userStatus.setMessage(userStatus.message());
|
||||
_userStatus.setMessagePredefined(userStatus.messagePredefined());
|
||||
_userStatus.setId(userStatus.id());
|
||||
_userStatus.setClearAt(userStatus.clearAt());
|
||||
|
||||
if (!userStatus.icon().isEmpty()) {
|
||||
_userStatus.setIcon(userStatus.icon());
|
||||
}
|
||||
|
||||
emit userStatusChanged();
|
||||
emit onlineStatusChanged();
|
||||
emit clearAtChanged();
|
||||
}
|
||||
|
||||
Optional<ClearAt> UserStatusSelectorModel::clearStageTypeToDateTime(ClearStageType type) const
|
||||
{
|
||||
switch (type) {
|
||||
case ClearStageType::DontClear:
|
||||
return {};
|
||||
|
||||
case ClearStageType::HalfHour: {
|
||||
ClearAt clearAt;
|
||||
clearAt._type = ClearAtType::Period;
|
||||
clearAt._period = 60 * 30;
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
case ClearStageType::OneHour: {
|
||||
ClearAt clearAt;
|
||||
clearAt._type = ClearAtType::Period;
|
||||
clearAt._period = 60 * 60;
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
case ClearStageType::FourHour: {
|
||||
ClearAt clearAt;
|
||||
clearAt._type = ClearAtType::Period;
|
||||
clearAt._period = 60 * 60 * 4;
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
case ClearStageType::Today: {
|
||||
ClearAt clearAt;
|
||||
clearAt._type = ClearAtType::EndOf;
|
||||
clearAt._endof = "day";
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
case ClearStageType::Week: {
|
||||
ClearAt clearAt;
|
||||
clearAt._type = ClearAtType::EndOf;
|
||||
clearAt._endof = "week";
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setUserStatus()
|
||||
{
|
||||
Q_ASSERT(_userStatusConnector);
|
||||
if (!_userStatusConnector) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearError();
|
||||
_userStatusConnector->setUserStatus(_userStatus);
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::clearUserStatus()
|
||||
{
|
||||
Q_ASSERT(_userStatusConnector);
|
||||
if (!_userStatusConnector) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearError();
|
||||
_userStatusConnector->clearMessage();
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::onPredefinedStatusesFetched(const std::vector<UserStatus> &statuses)
|
||||
{
|
||||
_predefinedStatuses = statuses;
|
||||
emit predefinedStatusesChanged();
|
||||
}
|
||||
|
||||
UserStatus UserStatusSelectorModel::predefinedStatus(int index) const
|
||||
{
|
||||
Q_ASSERT(0 <= index && index < static_cast<int>(_predefinedStatuses.size()));
|
||||
return _predefinedStatuses[index];
|
||||
}
|
||||
|
||||
int UserStatusSelectorModel::predefinedStatusesCount() const
|
||||
{
|
||||
return static_cast<int>(_predefinedStatuses.size());
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setPredefinedStatus(int index)
|
||||
{
|
||||
Q_ASSERT(0 <= index && index < static_cast<int>(_predefinedStatuses.size()));
|
||||
|
||||
_userStatus.setMessagePredefined(true);
|
||||
const auto predefinedStatus = _predefinedStatuses[index];
|
||||
_userStatus.setId(predefinedStatus.id());
|
||||
_userStatus.setMessage(predefinedStatus.message());
|
||||
_userStatus.setIcon(predefinedStatus.icon());
|
||||
_userStatus.setClearAt(predefinedStatus.clearAt());
|
||||
|
||||
emit userStatusChanged();
|
||||
emit clearAtChanged();
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::clearAtStageToString(ClearStageType stage) const
|
||||
{
|
||||
switch (stage) {
|
||||
case ClearStageType::DontClear:
|
||||
return tr("Don't clear");
|
||||
|
||||
case ClearStageType::HalfHour:
|
||||
return tr("30 minutes");
|
||||
|
||||
case ClearStageType::OneHour:
|
||||
return tr("1 hour");
|
||||
|
||||
case ClearStageType::FourHour:
|
||||
return tr("4 hours");
|
||||
|
||||
case ClearStageType::Today:
|
||||
return tr("Today");
|
||||
|
||||
case ClearStageType::Week:
|
||||
return tr("This week");
|
||||
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList UserStatusSelectorModel::clearAtValues() const
|
||||
{
|
||||
QStringList clearAtStages;
|
||||
std::transform(_clearStages.begin(), _clearStages.end(),
|
||||
std::back_inserter(clearAtStages),
|
||||
[this](const ClearStageType &stage) { return clearAtStageToString(stage); });
|
||||
|
||||
return clearAtStages;
|
||||
}
|
||||
|
||||
void UserStatusSelectorModel::setClearAt(int index)
|
||||
{
|
||||
Q_ASSERT(0 <= index && index < static_cast<int>(_clearStages.size()));
|
||||
_userStatus.setClearAt(clearStageTypeToDateTime(_clearStages[index]));
|
||||
emit clearAtChanged();
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::errorMessage() const
|
||||
{
|
||||
return _errorMessage;
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::timeDifferenceToString(int differenceSecs) const
|
||||
{
|
||||
if (differenceSecs < 60) {
|
||||
return tr("Less than a minute");
|
||||
} else if (differenceSecs < 60 * 60) {
|
||||
const auto minutesLeft = std::ceil(differenceSecs / 60.0);
|
||||
if (minutesLeft == 1) {
|
||||
return tr("1 minute");
|
||||
} else {
|
||||
return tr("%1 minutes").arg(minutesLeft);
|
||||
}
|
||||
} else if (differenceSecs < 60 * 60 * 24) {
|
||||
const auto hoursLeft = std::ceil(differenceSecs / 60.0 / 60.0);
|
||||
if (hoursLeft == 1) {
|
||||
return tr("1 hour");
|
||||
} else {
|
||||
return tr("%1 hours").arg(hoursLeft);
|
||||
}
|
||||
} else {
|
||||
const auto daysLeft = std::ceil(differenceSecs / 60.0 / 60.0 / 24.0);
|
||||
if (daysLeft == 1) {
|
||||
return tr("1 day");
|
||||
} else {
|
||||
return tr("%1 days").arg(daysLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::clearAtReadable(const Optional<ClearAt> &clearAt) const
|
||||
{
|
||||
if (clearAt) {
|
||||
switch (clearAt->_type) {
|
||||
case ClearAtType::Period: {
|
||||
return timeDifferenceToString(clearAt->_period);
|
||||
}
|
||||
|
||||
case ClearAtType::Timestamp: {
|
||||
const int difference = static_cast<int>(clearAt->_timestamp - _dateTimeProvider->currentDateTime().toTime_t());
|
||||
return timeDifferenceToString(difference);
|
||||
}
|
||||
|
||||
case ClearAtType::EndOf: {
|
||||
if (clearAt->_endof == "day") {
|
||||
return tr("Today");
|
||||
} else if (clearAt->_endof == "week") {
|
||||
return tr("This week");
|
||||
}
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
}
|
||||
return tr("Don't clear");
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::predefinedStatusClearAt(int index) const
|
||||
{
|
||||
return clearAtReadable(predefinedStatus(index).clearAt());
|
||||
}
|
||||
|
||||
QString UserStatusSelectorModel::clearAt() const
|
||||
{
|
||||
return clearAtReadable(_userStatus.clearAt());
|
||||
}
|
||||
}
|
145
src/gui/userstatusselectormodel.h
Normal file
145
src/gui/userstatusselectormodel.h
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/result.h"
|
||||
|
||||
#include <userstatusconnector.h>
|
||||
#include <datetimeprovider.h>
|
||||
|
||||
#include <QObject>
|
||||
#include <QMetaType>
|
||||
#include <QtNumeric>
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class UserStatusSelectorModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString userStatusMessage READ userStatusMessage NOTIFY userStatusChanged)
|
||||
Q_PROPERTY(QString userStatusEmoji READ userStatusEmoji WRITE setUserStatusEmoji NOTIFY userStatusChanged)
|
||||
Q_PROPERTY(OCC::UserStatus::OnlineStatus onlineStatus READ onlineStatus WRITE setOnlineStatus NOTIFY onlineStatusChanged)
|
||||
Q_PROPERTY(int predefinedStatusesCount READ predefinedStatusesCount NOTIFY predefinedStatusesChanged)
|
||||
Q_PROPERTY(QStringList clearAtValues READ clearAtValues CONSTANT)
|
||||
Q_PROPERTY(QString clearAt READ clearAt NOTIFY clearAtChanged)
|
||||
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
|
||||
Q_PROPERTY(QUrl onlineIcon READ onlineIcon CONSTANT)
|
||||
Q_PROPERTY(QUrl awayIcon READ awayIcon CONSTANT)
|
||||
Q_PROPERTY(QUrl dndIcon READ dndIcon CONSTANT)
|
||||
Q_PROPERTY(QUrl invisibleIcon READ invisibleIcon CONSTANT)
|
||||
|
||||
public:
|
||||
explicit UserStatusSelectorModel(QObject *parent = nullptr);
|
||||
|
||||
explicit UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
explicit UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
|
||||
std::unique_ptr<DateTimeProvider> dateTimeProvider,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
explicit UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
std::unique_ptr<DateTimeProvider> dateTimeProvider,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
explicit UserStatusSelectorModel(const UserStatus &userStatus,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
~UserStatusSelectorModel() override;
|
||||
|
||||
Q_INVOKABLE void load(int id);
|
||||
|
||||
Q_REQUIRED_RESULT UserStatus::OnlineStatus onlineStatus() const;
|
||||
Q_INVOKABLE void setOnlineStatus(OCC::UserStatus::OnlineStatus status);
|
||||
|
||||
Q_REQUIRED_RESULT QUrl onlineIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl awayIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl dndIcon() const;
|
||||
Q_REQUIRED_RESULT QUrl invisibleIcon() const;
|
||||
|
||||
Q_REQUIRED_RESULT QString userStatusMessage() const;
|
||||
Q_INVOKABLE void setUserStatusMessage(const QString &message);
|
||||
void setUserStatusEmoji(const QString &emoji);
|
||||
Q_REQUIRED_RESULT QString userStatusEmoji() const;
|
||||
|
||||
Q_INVOKABLE void setUserStatus();
|
||||
Q_INVOKABLE void clearUserStatus();
|
||||
|
||||
Q_REQUIRED_RESULT int predefinedStatusesCount() const;
|
||||
Q_INVOKABLE UserStatus predefinedStatus(int index) const;
|
||||
Q_INVOKABLE QString predefinedStatusClearAt(int index) const;
|
||||
Q_INVOKABLE void setPredefinedStatus(int index);
|
||||
|
||||
Q_REQUIRED_RESULT QStringList clearAtValues() const;
|
||||
Q_REQUIRED_RESULT QString clearAt() const;
|
||||
Q_INVOKABLE void setClearAt(int index);
|
||||
|
||||
Q_REQUIRED_RESULT QString errorMessage() const;
|
||||
|
||||
signals:
|
||||
void errorMessageChanged();
|
||||
void userStatusChanged();
|
||||
void onlineStatusChanged();
|
||||
void clearAtChanged();
|
||||
void predefinedStatusesChanged();
|
||||
void finished();
|
||||
|
||||
private:
|
||||
enum class ClearStageType {
|
||||
DontClear,
|
||||
HalfHour,
|
||||
OneHour,
|
||||
FourHour,
|
||||
Today,
|
||||
Week
|
||||
};
|
||||
|
||||
void init();
|
||||
void reset();
|
||||
void onUserStatusFetched(const UserStatus &userStatus);
|
||||
void onPredefinedStatusesFetched(const std::vector<UserStatus> &statuses);
|
||||
void onUserStatusSet();
|
||||
void onMessageCleared();
|
||||
void onError(UserStatusConnector::Error error);
|
||||
|
||||
Q_REQUIRED_RESULT QString clearAtStageToString(ClearStageType stage) const;
|
||||
Q_REQUIRED_RESULT QString clearAtReadable(const Optional<ClearAt> &clearAt) const;
|
||||
Q_REQUIRED_RESULT QString timeDifferenceToString(int differenceSecs) const;
|
||||
Q_REQUIRED_RESULT Optional<ClearAt> clearStageTypeToDateTime(ClearStageType type) const;
|
||||
void setError(const QString &reason);
|
||||
void clearError();
|
||||
|
||||
std::shared_ptr<UserStatusConnector> _userStatusConnector {};
|
||||
std::vector<UserStatus> _predefinedStatuses;
|
||||
UserStatus _userStatus;
|
||||
std::unique_ptr<DateTimeProvider> _dateTimeProvider;
|
||||
|
||||
QString _errorMessage;
|
||||
|
||||
std::vector<ClearStageType> _clearStages = {
|
||||
ClearStageType::DontClear,
|
||||
ClearStageType::HalfHour,
|
||||
ClearStageType::OneHour,
|
||||
ClearStageType::FourHour,
|
||||
ClearStageType::Today,
|
||||
ClearStageType::Week
|
||||
};
|
||||
};
|
||||
}
|
|
@ -55,6 +55,9 @@ set(libsync_SRCS
|
|||
theme.cpp
|
||||
clientsideencryption.cpp
|
||||
clientsideencryptionjobs.cpp
|
||||
datetimeprovider.cpp
|
||||
ocsuserstatusconnector.cpp
|
||||
userstatusconnector.cpp
|
||||
creds/dummycredentials.cpp
|
||||
creds/abstractcredentials.cpp
|
||||
creds/credentialscommon.cpp
|
||||
|
|
|
@ -139,6 +139,15 @@ QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUr
|
|||
return reply;
|
||||
}
|
||||
|
||||
QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUrl &url,
|
||||
QNetworkRequest req, const QByteArray &requestBody)
|
||||
{
|
||||
auto reply = _account->sendRawRequest(verb, url, req, requestBody);
|
||||
_requestBody = nullptr;
|
||||
adoptRequest(reply);
|
||||
return reply;
|
||||
}
|
||||
|
||||
void AbstractNetworkJob::adoptRequest(QNetworkReply *reply)
|
||||
{
|
||||
addTimer(reply);
|
||||
|
|
|
@ -128,6 +128,9 @@ protected:
|
|||
QNetworkRequest req = QNetworkRequest(),
|
||||
QIODevice *requestBody = nullptr);
|
||||
|
||||
QNetworkReply *sendRequest(const QByteArray &verb, const QUrl &url,
|
||||
QNetworkRequest req, const QByteArray &requestBody);
|
||||
|
||||
// sendRequest does not take a relative path instead of an url,
|
||||
// but the old API allowed that. We have this undefined overload
|
||||
// to help catch usage errors
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
*/
|
||||
|
||||
#include "account.h"
|
||||
#include "accountfwd.h"
|
||||
#include "clientsideencryptionjobs.h"
|
||||
#include "cookiejar.h"
|
||||
#include "networkjobs.h"
|
||||
#include "configfile.h"
|
||||
|
@ -27,6 +29,7 @@
|
|||
|
||||
#include "common/asserts.h"
|
||||
#include "clientsideencryption.h"
|
||||
#include "ocsuserstatusconnector.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QNetworkReply>
|
||||
|
@ -43,6 +46,7 @@
|
|||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
#include <qsslconfiguration.h>
|
||||
#include <qt5keychain/keychain.h>
|
||||
|
@ -93,6 +97,7 @@ QString Account::davPath() const
|
|||
void Account::setSharedThis(AccountPtr sharedThis)
|
||||
{
|
||||
_sharedThis = sharedThis.toWeakRef();
|
||||
setupUserStatusConnector();
|
||||
}
|
||||
|
||||
QString Account::davPathBase()
|
||||
|
@ -337,6 +342,24 @@ QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url,
|
|||
return _am->sendCustomRequest(req, verb, data);
|
||||
}
|
||||
|
||||
QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, const QByteArray &data)
|
||||
{
|
||||
req.setUrl(url);
|
||||
req.setSslConfiguration(this->getOrCreateSslConfig());
|
||||
if (verb == "HEAD" && data.isEmpty()) {
|
||||
return _am->head(req);
|
||||
} else if (verb == "GET" && data.isEmpty()) {
|
||||
return _am->get(req);
|
||||
} else if (verb == "POST") {
|
||||
return _am->post(req, data);
|
||||
} else if (verb == "PUT") {
|
||||
return _am->put(req, data);
|
||||
} else if (verb == "DELETE" && data.isEmpty()) {
|
||||
return _am->deleteResource(req);
|
||||
}
|
||||
return _am->sendCustomRequest(req, verb, data);
|
||||
}
|
||||
|
||||
SimpleNetworkJob *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
|
||||
{
|
||||
auto job = new SimpleNetworkJob(sharedFromThis());
|
||||
|
@ -544,9 +567,18 @@ void Account::setCapabilities(const QVariantMap &caps)
|
|||
{
|
||||
_capabilities = Capabilities(caps);
|
||||
|
||||
setupUserStatusConnector();
|
||||
trySetupPushNotifications();
|
||||
}
|
||||
|
||||
void Account::setupUserStatusConnector()
|
||||
{
|
||||
_userStatusConnector = std::make_shared<OcsUserStatusConnector>(sharedFromThis());
|
||||
connect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this, [this](const UserStatus &) {
|
||||
emit userStatusChanged();
|
||||
});
|
||||
}
|
||||
|
||||
QString Account::serverVersion() const
|
||||
{
|
||||
return _serverVersion;
|
||||
|
@ -744,4 +776,9 @@ PushNotifications *Account::pushNotifications() const
|
|||
return _pushNotifications;
|
||||
}
|
||||
|
||||
std::shared_ptr<UserStatusConnector> Account::userStatusConnector() const
|
||||
{
|
||||
return _userStatusConnector;
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -55,6 +55,7 @@ using AccountPtr = QSharedPointer<Account>;
|
|||
class AccessManager;
|
||||
class SimpleNetworkJob;
|
||||
class PushNotifications;
|
||||
class UserStatusConnector;
|
||||
|
||||
/**
|
||||
* @brief Reimplement this to handle SSL errors from libsync
|
||||
|
@ -150,6 +151,9 @@ public:
|
|||
QNetworkRequest req = QNetworkRequest(),
|
||||
QIODevice *data = nullptr);
|
||||
|
||||
QNetworkReply *sendRawRequest(const QByteArray &verb,
|
||||
const QUrl &url, QNetworkRequest req, const QByteArray &data);
|
||||
|
||||
/** Create and start network job for a simple one-off request.
|
||||
*
|
||||
* More complicated requests typically create their own job types.
|
||||
|
@ -251,10 +255,13 @@ public:
|
|||
// Check for the directEditing capability
|
||||
void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);
|
||||
|
||||
void setupUserStatusConnector();
|
||||
void trySetupPushNotifications();
|
||||
PushNotifications *pushNotifications() const;
|
||||
void setPushNotificationsReconnectInterval(int interval);
|
||||
|
||||
std::shared_ptr<UserStatusConnector> userStatusConnector() const;
|
||||
|
||||
public slots:
|
||||
/// Used when forgetting credentials
|
||||
void clearQNAMCache();
|
||||
|
@ -287,6 +294,8 @@ signals:
|
|||
void pushNotificationsReady(Account *account);
|
||||
void pushNotificationsDisabled(Account *account);
|
||||
|
||||
void userStatusChanged();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void slotCredentialsFetched();
|
||||
void slotCredentialsAsked();
|
||||
|
@ -343,6 +352,8 @@ private:
|
|||
|
||||
PushNotifications *_pushNotifications = nullptr;
|
||||
|
||||
std::shared_ptr<UserStatusConnector> _userStatusConnector;
|
||||
|
||||
/* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
|
||||
* TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
|
||||
*
|
||||
|
|
|
@ -187,13 +187,31 @@ bool Capabilities::chunkingNg() const
|
|||
return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
|
||||
}
|
||||
|
||||
bool Capabilities::userStatus() const
|
||||
bool Capabilities::userStatusNotification() const
|
||||
{
|
||||
return _capabilities.contains("notifications") &&
|
||||
_capabilities["notifications"].toMap().contains("ocs-endpoints") &&
|
||||
_capabilities["notifications"].toMap()["ocs-endpoints"].toStringList().contains("user-status");
|
||||
}
|
||||
|
||||
bool Capabilities::userStatus() const
|
||||
{
|
||||
if (!_capabilities.contains("user_status")) {
|
||||
return false;
|
||||
}
|
||||
const auto userStatusMap = _capabilities["user_status"].toMap();
|
||||
return userStatusMap.value("enabled", false).toBool();
|
||||
}
|
||||
|
||||
bool Capabilities::userStatusSupportsEmoji() const
|
||||
{
|
||||
if (!userStatus()) {
|
||||
return false;
|
||||
}
|
||||
const auto userStatusMap = _capabilities["user_status"].toMap();
|
||||
return userStatusMap.value("supports_emoji", false).toBool();
|
||||
}
|
||||
|
||||
PushNotificationTypes Capabilities::availablePushNotifications() const
|
||||
{
|
||||
if (!_capabilities.contains("notify_push")) {
|
||||
|
|
|
@ -58,7 +58,9 @@ public:
|
|||
bool sharePublicLinkMultiple() const;
|
||||
bool shareResharing() const;
|
||||
bool chunkingNg() const;
|
||||
bool userStatusNotification() const;
|
||||
bool userStatus() const;
|
||||
bool userStatusSupportsEmoji() const;
|
||||
|
||||
/// Returns which kind of push notfications are available
|
||||
PushNotificationTypes availablePushNotifications() const;
|
||||
|
|
|
@ -56,7 +56,8 @@ Q_LOGGING_CATEGORY(lcCse, "nextcloud.sync.clientsideencryption", QtInfoMsg)
|
|||
Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.e2e", QtInfoMsg)
|
||||
Q_LOGGING_CATEGORY(lcCseMetadata, "nextcloud.metadata", QtInfoMsg)
|
||||
|
||||
QString baseUrl(){
|
||||
QString e2eeBaseUrl()
|
||||
{
|
||||
return QStringLiteral("ocs/v2.php/apps/end_to_end_encryption/api/v1/");
|
||||
}
|
||||
|
||||
|
@ -1180,7 +1181,7 @@ void ClientSideEncryption::generateCSR(const AccountPtr &account, EVP_PKEY *keyP
|
|||
qCInfo(lcCse()) << "Returning the certificate";
|
||||
qCInfo(lcCse()) << output;
|
||||
|
||||
auto job = new SignPublicKeyApiJob(account, baseUrl() + "public-key", this);
|
||||
auto job = new SignPublicKeyApiJob(account, e2eeBaseUrl() + "public-key", this);
|
||||
job->setCsr(output);
|
||||
|
||||
connect(job, &SignPublicKeyApiJob::jsonReceived, [this, account](const QJsonDocument& json, int retCode) {
|
||||
|
@ -1212,7 +1213,7 @@ void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
|
|||
auto cryptedText = EncryptionHelper::encryptPrivateKey(secretKey, EncryptionHelper::privateKeyToPem(_privateKey), salt);
|
||||
|
||||
// Send private key to the server
|
||||
auto job = new StorePrivateKeyApiJob(account, baseUrl() + "private-key", this);
|
||||
auto job = new StorePrivateKeyApiJob(account, e2eeBaseUrl() + "private-key", this);
|
||||
job->setPrivateKey(cryptedText);
|
||||
connect(job, &StorePrivateKeyApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
Q_UNUSED(doc);
|
||||
|
@ -1296,7 +1297,7 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
|
|||
void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving private key from server";
|
||||
auto job = new JsonApiJob(account, baseUrl() + "private-key", this);
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "private-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
QString key = doc.object()["ocs"].toObject()["data"].toObject()["private-key"].toString();
|
||||
|
@ -1315,7 +1316,7 @@ void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
|
|||
void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving public key from server";
|
||||
auto job = new JsonApiJob(account, baseUrl() + "public-key", this);
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "public-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
QString publicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-keys"].toObject()[account->davUser()].toString();
|
||||
|
@ -1336,7 +1337,7 @@ void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
|
|||
void ClientSideEncryption::fetchAndValidatePublicKeyFromServer(const AccountPtr &account)
|
||||
{
|
||||
qCInfo(lcCse()) << "Retrieving public key from server";
|
||||
auto job = new JsonApiJob(account, baseUrl() + "server-key", this);
|
||||
auto job = new JsonApiJob(account, e2eeBaseUrl() + "server-key", this);
|
||||
connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
|
||||
if (retCode == 200) {
|
||||
const auto serverPublicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-key"].toString().toLatin1();
|
||||
|
|
|
@ -23,7 +23,7 @@ class ReadPasswordJob;
|
|||
|
||||
namespace OCC {
|
||||
|
||||
QString baseUrl();
|
||||
QString e2eeBaseUrl();
|
||||
|
||||
namespace EncryptionHelper {
|
||||
QByteArray generateRandomFilename();
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace OCC {
|
|||
GetMetadataApiJob::GetMetadataApiJob(const AccountPtr& account,
|
||||
const QByteArray& fileId,
|
||||
QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ StoreMetaDataApiJob::StoreMetaDataApiJob(const AccountPtr& account,
|
|||
const QByteArray& fileId,
|
||||
const QByteArray& b64Metadata,
|
||||
QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId), _b64Metadata(b64Metadata)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId), _b64Metadata(b64Metadata)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -104,8 +104,8 @@ UpdateMetadataApiJob::UpdateMetadataApiJob(const AccountPtr& account,
|
|||
const QByteArray& b64Metadata,
|
||||
const QByteArray& token,
|
||||
QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent),
|
||||
_fileId(fileId),
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent)
|
||||
, _fileId(fileId),
|
||||
_b64Metadata(b64Metadata),
|
||||
_token(token)
|
||||
{
|
||||
|
@ -154,7 +154,7 @@ UnlockEncryptFolderApiJob::UnlockEncryptFolderApiJob(const AccountPtr& account,
|
|||
const QByteArray& fileId,
|
||||
const QByteArray& token,
|
||||
QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -185,11 +185,10 @@ bool UnlockEncryptFolderApiJob::finished()
|
|||
}
|
||||
|
||||
|
||||
|
||||
DeleteMetadataApiJob::DeleteMetadataApiJob(const AccountPtr& account,
|
||||
const QByteArray& fileId,
|
||||
QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -219,7 +218,7 @@ bool DeleteMetadataApiJob::finished()
|
|||
}
|
||||
|
||||
LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -258,7 +257,7 @@ bool LockEncryptFolderApiJob::finished()
|
|||
}
|
||||
|
||||
SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, FlagAction flagAction, QObject* parent)
|
||||
: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
|
||||
: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
17
src/libsync/datetimeprovider.cpp
Normal file
17
src/libsync/datetimeprovider.cpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#include "datetimeprovider.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
DateTimeProvider::~DateTimeProvider() = default;
|
||||
|
||||
QDateTime DateTimeProvider::currentDateTime() const
|
||||
{
|
||||
return QDateTime::currentDateTime();
|
||||
}
|
||||
|
||||
QDate DateTimeProvider::currentDate() const
|
||||
{
|
||||
return QDate::currentDate();
|
||||
}
|
||||
|
||||
}
|
18
src/libsync/datetimeprovider.h
Normal file
18
src/libsync/datetimeprovider.h
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "owncloudlib.h"
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class OWNCLOUDSYNC_EXPORT DateTimeProvider
|
||||
{
|
||||
public:
|
||||
virtual ~DateTimeProvider();
|
||||
|
||||
virtual QDateTime currentDateTime() const;
|
||||
|
||||
virtual QDate currentDate() const;
|
||||
};
|
||||
}
|
|
@ -830,13 +830,49 @@ void JsonApiJob::addRawHeader(const QByteArray &headerName, const QByteArray &va
|
|||
_request.setRawHeader(headerName, value);
|
||||
}
|
||||
|
||||
void JsonApiJob::setBody(const QJsonDocument &body)
|
||||
{
|
||||
_body = body.toJson();
|
||||
qCDebug(lcJsonApiJob) << "Set body for request:" << _body;
|
||||
if (!_body.isEmpty()) {
|
||||
_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JsonApiJob::setVerb(Verb value)
|
||||
{
|
||||
_verb = value;
|
||||
}
|
||||
|
||||
|
||||
QByteArray JsonApiJob::verbToString() const
|
||||
{
|
||||
switch (_verb) {
|
||||
case Verb::Get:
|
||||
return "GET";
|
||||
case Verb::Post:
|
||||
return "POST";
|
||||
case Verb::Put:
|
||||
return "PUT";
|
||||
case Verb::Delete:
|
||||
return "DELETE";
|
||||
}
|
||||
return "GET";
|
||||
}
|
||||
|
||||
void JsonApiJob::start()
|
||||
{
|
||||
addRawHeader("OCS-APIREQUEST", "true");
|
||||
auto query = _additionalParams;
|
||||
query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
|
||||
QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
|
||||
sendRequest(_usePOST ? "POST" : "GET", url, _request);
|
||||
const auto httpVerb = verbToString();
|
||||
if (!_body.isEmpty()) {
|
||||
sendRequest(httpVerb, url, _request, _body);
|
||||
} else {
|
||||
sendRequest(httpVerb, url, _request);
|
||||
}
|
||||
AbstractNetworkJob::start();
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <QBuffer>
|
||||
#include <QUrlQuery>
|
||||
#include <QJsonDocument>
|
||||
#include <functional>
|
||||
|
||||
class QUrl;
|
||||
|
@ -375,6 +376,13 @@ class OWNCLOUDSYNC_EXPORT JsonApiJob : public AbstractNetworkJob
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class Verb {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
};
|
||||
|
||||
explicit JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
|
@ -390,15 +398,9 @@ public:
|
|||
void addQueryParams(const QUrlQuery ¶ms);
|
||||
void addRawHeader(const QByteArray &headerName, const QByteArray &value);
|
||||
|
||||
/**
|
||||
* @brief usePOST - allow job to do an anonymous POST request instead of GET
|
||||
* @param params: (optional) true for POST, false for GET (default).
|
||||
*
|
||||
* This function needs to be called before start() obviously.
|
||||
*/
|
||||
void usePOST(bool usePOST = true) {
|
||||
_usePOST = usePOST;
|
||||
}
|
||||
void setBody(const QJsonDocument &body);
|
||||
|
||||
void setVerb(Verb value);
|
||||
|
||||
public slots:
|
||||
void start() override;
|
||||
|
@ -429,10 +431,13 @@ signals:
|
|||
void allowDesktopNotificationsChanged(bool isAllowed);
|
||||
|
||||
private:
|
||||
QByteArray _body;
|
||||
QUrlQuery _additionalParams;
|
||||
QNetworkRequest _request;
|
||||
|
||||
bool _usePOST = false;
|
||||
Verb _verb = Verb::Get;
|
||||
|
||||
QByteArray verbToString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
455
src/libsync/ocsuserstatusconnector.cpp
Normal file
455
src/libsync/ocsuserstatusconnector.cpp
Normal file
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#include "ocsuserstatusconnector.h"
|
||||
#include "account.h"
|
||||
#include "userstatusconnector.h"
|
||||
|
||||
#include <networkjobs.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QtGlobal>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonValue>
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <qdatetime.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsonobject.h>
|
||||
#include <qloggingcategory.h>
|
||||
|
||||
namespace {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcOcsUserStatusConnector, "nextcloud.gui.ocsuserstatusconnector", QtInfoMsg)
|
||||
|
||||
OCC::UserStatus::OnlineStatus stringToUserOnlineStatus(const QString &status)
|
||||
{
|
||||
// it needs to match the Status enum
|
||||
const QHash<QString, OCC::UserStatus::OnlineStatus> preDefinedStatus {
|
||||
{ "online", OCC::UserStatus::OnlineStatus::Online },
|
||||
{ "dnd", OCC::UserStatus::OnlineStatus::DoNotDisturb },
|
||||
{ "away", OCC::UserStatus::OnlineStatus::Away },
|
||||
{ "offline", OCC::UserStatus::OnlineStatus::Offline },
|
||||
{ "invisible", OCC::UserStatus::OnlineStatus::Invisible }
|
||||
};
|
||||
|
||||
// api should return invisible, dnd,... toLower() it is to make sure
|
||||
// it matches _preDefinedStatus, otherwise the default is online (0)
|
||||
return preDefinedStatus.value(status.toLower(), OCC::UserStatus::OnlineStatus::Online);
|
||||
}
|
||||
|
||||
QString onlineStatusToString(OCC::UserStatus::OnlineStatus status)
|
||||
{
|
||||
switch (status) {
|
||||
case OCC::UserStatus::OnlineStatus::Online:
|
||||
return QStringLiteral("online");
|
||||
case OCC::UserStatus::OnlineStatus::DoNotDisturb:
|
||||
return QStringLiteral("dnd");
|
||||
case OCC::UserStatus::OnlineStatus::Away:
|
||||
return QStringLiteral("offline");
|
||||
case OCC::UserStatus::OnlineStatus::Offline:
|
||||
return QStringLiteral("offline");
|
||||
case OCC::UserStatus::OnlineStatus::Invisible:
|
||||
return QStringLiteral("invisible");
|
||||
}
|
||||
return QStringLiteral("online");
|
||||
}
|
||||
|
||||
OCC::Optional<OCC::ClearAt> jsonExtractClearAt(QJsonObject jsonObject)
|
||||
{
|
||||
OCC::Optional<OCC::ClearAt> clearAt {};
|
||||
if (jsonObject.contains("clearAt") && !jsonObject.value("clearAt").isNull()) {
|
||||
OCC::ClearAt clearAtValue;
|
||||
clearAtValue._type = OCC::ClearAtType::Timestamp;
|
||||
clearAtValue._timestamp = jsonObject.value("clearAt").toInt();
|
||||
clearAt = clearAtValue;
|
||||
}
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
OCC::UserStatus jsonExtractUserStatus(QJsonObject json)
|
||||
{
|
||||
const auto clearAt = jsonExtractClearAt(json);
|
||||
|
||||
const OCC::UserStatus userStatus(json.value("messageId").toString(),
|
||||
json.value("message").toString().trimmed(),
|
||||
json.value("icon").toString().trimmed(), stringToUserOnlineStatus(json.value("status").toString()),
|
||||
json.value("messageIsPredefined").toBool(false), clearAt);
|
||||
|
||||
return userStatus;
|
||||
}
|
||||
|
||||
OCC::UserStatus jsonToUserStatus(const QJsonDocument &json)
|
||||
{
|
||||
const QJsonObject defaultValues {
|
||||
{ "icon", "" },
|
||||
{ "message", "" },
|
||||
{ "status", "online" },
|
||||
{ "messageIsPredefined", "false" },
|
||||
{ "statusIsUserDefined", "false" }
|
||||
};
|
||||
const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject(defaultValues);
|
||||
return jsonExtractUserStatus(retrievedData);
|
||||
}
|
||||
|
||||
quint64 clearAtEndOfToTimestamp(const OCC::ClearAt &clearAt)
|
||||
{
|
||||
Q_ASSERT(clearAt._type == OCC::ClearAtType::EndOf);
|
||||
|
||||
if (clearAt._endof == "day") {
|
||||
return QDate::currentDate().addDays(1).startOfDay().toTime_t();
|
||||
} else if (clearAt._endof == "week") {
|
||||
const auto days = Qt::Sunday - QDate::currentDate().dayOfWeek();
|
||||
return QDate::currentDate().addDays(days + 1).startOfDay().toTime_t();
|
||||
}
|
||||
qCWarning(lcOcsUserStatusConnector) << "Can not handle clear at endof day type" << clearAt._endof;
|
||||
return QDateTime::currentDateTime().toTime_t();
|
||||
}
|
||||
|
||||
quint64 clearAtPeriodToTimestamp(const OCC::ClearAt &clearAt)
|
||||
{
|
||||
return QDateTime::currentDateTime().addSecs(clearAt._period).toTime_t();
|
||||
}
|
||||
|
||||
quint64 clearAtToTimestamp(const OCC::ClearAt &clearAt)
|
||||
{
|
||||
switch (clearAt._type) {
|
||||
case OCC::ClearAtType::Period: {
|
||||
return clearAtPeriodToTimestamp(clearAt);
|
||||
}
|
||||
|
||||
case OCC::ClearAtType::EndOf: {
|
||||
return clearAtEndOfToTimestamp(clearAt);
|
||||
}
|
||||
|
||||
case OCC::ClearAtType::Timestamp: {
|
||||
return clearAt._timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
quint64 clearAtToTimestamp(const OCC::Optional<OCC::ClearAt> &clearAt)
|
||||
{
|
||||
if (clearAt) {
|
||||
return clearAtToTimestamp(*clearAt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
OCC::Optional<OCC::ClearAt> jsonToClearAt(QJsonObject jsonObject)
|
||||
{
|
||||
OCC::Optional<OCC::ClearAt> clearAt;
|
||||
|
||||
if (jsonObject.value("clearAt").isObject() && !jsonObject.value("clearAt").isNull()) {
|
||||
OCC::ClearAt clearAtValue;
|
||||
const auto clearAtObject = jsonObject.value("clearAt").toObject();
|
||||
const auto typeValue = clearAtObject.value("type").toString("period");
|
||||
if (typeValue == "period") {
|
||||
const auto timeValue = clearAtObject.value("time").toInt(0);
|
||||
clearAtValue._type = OCC::ClearAtType::Period;
|
||||
clearAtValue._period = timeValue;
|
||||
} else if (typeValue == "end-of") {
|
||||
const auto timeValue = clearAtObject.value("time").toString("day");
|
||||
clearAtValue._type = OCC::ClearAtType::EndOf;
|
||||
clearAtValue._endof = timeValue;
|
||||
} else {
|
||||
qCWarning(lcOcsUserStatusConnector) << "Can not handle clear type value" << typeValue;
|
||||
}
|
||||
clearAt = clearAtValue;
|
||||
}
|
||||
|
||||
return clearAt;
|
||||
}
|
||||
|
||||
OCC::UserStatus jsonToUserStatus(QJsonObject jsonObject)
|
||||
{
|
||||
const auto clearAt = jsonToClearAt(jsonObject);
|
||||
|
||||
OCC::UserStatus userStatus(
|
||||
jsonObject.value("id").toString("no-id"),
|
||||
jsonObject.value("message").toString("No message"),
|
||||
jsonObject.value("icon").toString("no-icon"),
|
||||
OCC::UserStatus::OnlineStatus::Online,
|
||||
true,
|
||||
clearAt);
|
||||
|
||||
return userStatus;
|
||||
}
|
||||
|
||||
std::vector<OCC::UserStatus> jsonToPredefinedStatuses(QJsonArray jsonDataArray)
|
||||
{
|
||||
std::vector<OCC::UserStatus> statuses;
|
||||
for (const auto &jsonEntry : jsonDataArray) {
|
||||
Q_ASSERT(jsonEntry.isObject());
|
||||
if (!jsonEntry.isObject()) {
|
||||
continue;
|
||||
}
|
||||
statuses.push_back(jsonToUserStatus(jsonEntry.toObject()));
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
|
||||
const QString baseUrl("/ocs/v2.php/apps/user_status/api/v1");
|
||||
const QString userStatusBaseUrl = baseUrl + QStringLiteral("/user_status");
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
OcsUserStatusConnector::OcsUserStatusConnector(AccountPtr account, QObject *parent)
|
||||
: UserStatusConnector(parent)
|
||||
, _account(account)
|
||||
{
|
||||
Q_ASSERT(_account);
|
||||
_userStatusSupported = _account->capabilities().userStatus();
|
||||
_userStatusEmojisSupported = _account->capabilities().userStatusSupportsEmoji();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::fetchUserStatus()
|
||||
{
|
||||
qCDebug(lcOcsUserStatusConnector) << "Try to fetch user status";
|
||||
|
||||
if (!_userStatusSupported) {
|
||||
qCDebug(lcOcsUserStatusConnector) << "User status not supported";
|
||||
emit error(Error::UserStatusNotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
startFetchUserStatusJob();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::startFetchUserStatusJob()
|
||||
{
|
||||
if (_getUserStatusJob) {
|
||||
qCDebug(lcOcsUserStatusConnector) << "Get user status job is already running.";
|
||||
return;
|
||||
}
|
||||
|
||||
_getUserStatusJob = new JsonApiJob(_account, userStatusBaseUrl, this);
|
||||
connect(_getUserStatusJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusFetched);
|
||||
_getUserStatusJob->start();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::onUserStatusFetched(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
logResponse("user status fetched", json, statusCode);
|
||||
|
||||
if (statusCode != 200) {
|
||||
qCInfo(lcOcsUserStatusConnector) << "Slot fetch UserStatus finished with status code" << statusCode;
|
||||
emit error(Error::CouldNotFetchUserStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
_userStatus = jsonToUserStatus(json);
|
||||
emit userStatusFetched(_userStatus);
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::startFetchPredefinedStatuses()
|
||||
{
|
||||
if (_getPredefinedStausesJob) {
|
||||
qCDebug(lcOcsUserStatusConnector) << "Get predefined statuses job is already running";
|
||||
return;
|
||||
}
|
||||
|
||||
_getPredefinedStausesJob = new JsonApiJob(_account,
|
||||
baseUrl + QStringLiteral("/predefined_statuses"), this);
|
||||
connect(_getPredefinedStausesJob, &JsonApiJob::jsonReceived, this,
|
||||
&OcsUserStatusConnector::onPredefinedStatusesFetched);
|
||||
_getPredefinedStausesJob->start();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::fetchPredefinedStatuses()
|
||||
{
|
||||
if (!_userStatusSupported) {
|
||||
emit error(Error::UserStatusNotSupported);
|
||||
return;
|
||||
}
|
||||
startFetchPredefinedStatuses();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::onPredefinedStatusesFetched(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
logResponse("predefined statuses", json, statusCode);
|
||||
|
||||
if (statusCode != 200) {
|
||||
qCInfo(lcOcsUserStatusConnector) << "Slot predefined user statuses finished with status code" << statusCode;
|
||||
emit error(Error::CouldNotFetchPredefinedUserStatuses);
|
||||
return;
|
||||
}
|
||||
const auto jsonData = json.object().value("ocs").toObject().value("data");
|
||||
Q_ASSERT(jsonData.isArray());
|
||||
if (!jsonData.isArray()) {
|
||||
return;
|
||||
}
|
||||
const auto statuses = jsonToPredefinedStatuses(jsonData.toArray());
|
||||
emit predefinedStatusesFetched(statuses);
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::logResponse(const QString &message, const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
qCDebug(lcOcsUserStatusConnector) << "Response from:" << message << "Status:" << statusCode << "Json:" << json;
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::setUserStatusOnlineStatus(UserStatus::OnlineStatus onlineStatus)
|
||||
{
|
||||
_setOnlineStatusJob = new JsonApiJob(_account,
|
||||
userStatusBaseUrl + QStringLiteral("/status"), this);
|
||||
_setOnlineStatusJob->setVerb(JsonApiJob::Verb::Put);
|
||||
// Set body
|
||||
QJsonObject dataObject;
|
||||
dataObject.insert("statusType", onlineStatusToString(onlineStatus));
|
||||
QJsonDocument body;
|
||||
body.setObject(dataObject);
|
||||
_setOnlineStatusJob->setBody(body);
|
||||
connect(_setOnlineStatusJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusOnlineStatusSet);
|
||||
_setOnlineStatusJob->start();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::setUserStatusMessagePredefined(const UserStatus &userStatus)
|
||||
{
|
||||
Q_ASSERT(userStatus.messagePredefined());
|
||||
if (!userStatus.messagePredefined()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_setMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message/predefined"), this);
|
||||
_setMessageJob->setVerb(JsonApiJob::Verb::Put);
|
||||
// Set body
|
||||
QJsonObject dataObject;
|
||||
dataObject.insert("messageId", userStatus.id());
|
||||
if (userStatus.clearAt()) {
|
||||
dataObject.insert("clearAt", static_cast<int>(clearAtToTimestamp(userStatus.clearAt())));
|
||||
} else {
|
||||
dataObject.insert("clearAt", QJsonValue());
|
||||
}
|
||||
QJsonDocument body;
|
||||
body.setObject(dataObject);
|
||||
_setMessageJob->setBody(body);
|
||||
connect(_setMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusMessageSet);
|
||||
_setMessageJob->start();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::setUserStatusMessageCustom(const UserStatus &userStatus)
|
||||
{
|
||||
Q_ASSERT(!userStatus.messagePredefined());
|
||||
if (userStatus.messagePredefined()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_userStatusEmojisSupported) {
|
||||
emit error(Error::EmojisNotSupported);
|
||||
return;
|
||||
}
|
||||
_setMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message/custom"), this);
|
||||
_setMessageJob->setVerb(JsonApiJob::Verb::Put);
|
||||
// Set body
|
||||
QJsonObject dataObject;
|
||||
dataObject.insert("statusIcon", userStatus.icon());
|
||||
dataObject.insert("message", userStatus.message());
|
||||
const auto clearAt = userStatus.clearAt();
|
||||
if (clearAt) {
|
||||
dataObject.insert("clearAt", static_cast<int>(clearAtToTimestamp(*clearAt)));
|
||||
} else {
|
||||
dataObject.insert("clearAt", QJsonValue());
|
||||
}
|
||||
QJsonDocument body;
|
||||
body.setObject(dataObject);
|
||||
_setMessageJob->setBody(body);
|
||||
connect(_setMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusMessageSet);
|
||||
_setMessageJob->start();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::setUserStatusMessage(const UserStatus &userStatus)
|
||||
{
|
||||
if (userStatus.messagePredefined()) {
|
||||
setUserStatusMessagePredefined(userStatus);
|
||||
return;
|
||||
}
|
||||
setUserStatusMessageCustom(userStatus);
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::setUserStatus(const UserStatus &userStatus)
|
||||
{
|
||||
if (!_userStatusSupported) {
|
||||
emit error(Error::UserStatusNotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_setOnlineStatusJob || _setMessageJob) {
|
||||
qCDebug(lcOcsUserStatusConnector) << "Set online status job or set message job are already running.";
|
||||
return;
|
||||
}
|
||||
|
||||
setUserStatusOnlineStatus(userStatus.state());
|
||||
setUserStatusMessage(userStatus);
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::onUserStatusOnlineStatusSet(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
logResponse("Online status set", json, statusCode);
|
||||
|
||||
if (statusCode != 200) {
|
||||
emit error(Error::CouldNotSetUserStatus);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::onUserStatusMessageSet(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
logResponse("Message set", json, statusCode);
|
||||
|
||||
if (statusCode != 200) {
|
||||
emit error(Error::CouldNotSetUserStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
// We fetch the user status again because json does not contain
|
||||
// the new message when user status was set from a predefined
|
||||
// message
|
||||
fetchUserStatus();
|
||||
|
||||
emit userStatusSet();
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::clearMessage()
|
||||
{
|
||||
_clearMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message"));
|
||||
_clearMessageJob->setVerb(JsonApiJob::Verb::Delete);
|
||||
connect(_clearMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onMessageCleared);
|
||||
_clearMessageJob->start();
|
||||
}
|
||||
|
||||
UserStatus OcsUserStatusConnector::userStatus() const
|
||||
{
|
||||
return _userStatus;
|
||||
}
|
||||
|
||||
void OcsUserStatusConnector::onMessageCleared(const QJsonDocument &json, int statusCode)
|
||||
{
|
||||
logResponse("Message cleared", json, statusCode);
|
||||
|
||||
if (statusCode != 200) {
|
||||
emit error(Error::CouldNotClearMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
_userStatus = {};
|
||||
emit messageCleared();
|
||||
}
|
||||
}
|
70
src/libsync/ocsuserstatusconnector.h
Normal file
70
src/libsync/ocsuserstatusconnector.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "accountfwd.h"
|
||||
#include "userstatusconnector.h"
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class JsonApiJob;
|
||||
class SimpleNetworkJob;
|
||||
|
||||
class OWNCLOUDSYNC_EXPORT OcsUserStatusConnector : public UserStatusConnector
|
||||
{
|
||||
public:
|
||||
explicit OcsUserStatusConnector(AccountPtr account, QObject *parent = nullptr);
|
||||
|
||||
void fetchUserStatus() override;
|
||||
|
||||
void fetchPredefinedStatuses() override;
|
||||
|
||||
void setUserStatus(const UserStatus &userStatus) override;
|
||||
|
||||
void clearMessage() override;
|
||||
|
||||
UserStatus userStatus() const override;
|
||||
|
||||
private:
|
||||
void onUserStatusFetched(const QJsonDocument &json, int statusCode);
|
||||
void onPredefinedStatusesFetched(const QJsonDocument &json, int statusCode);
|
||||
void onUserStatusOnlineStatusSet(const QJsonDocument &json, int statusCode);
|
||||
void onUserStatusMessageSet(const QJsonDocument &json, int statusCode);
|
||||
void onMessageCleared(const QJsonDocument &json, int statusCode);
|
||||
|
||||
void logResponse(const QString &message, const QJsonDocument &json, int statusCode);
|
||||
void startFetchUserStatusJob();
|
||||
void startFetchPredefinedStatuses();
|
||||
void setUserStatusOnlineStatus(UserStatus::OnlineStatus onlineStatus);
|
||||
void setUserStatusMessage(const UserStatus &userStatus);
|
||||
void setUserStatusMessagePredefined(const UserStatus &userStatus);
|
||||
void setUserStatusMessageCustom(const UserStatus &userStatus);
|
||||
|
||||
AccountPtr _account;
|
||||
|
||||
bool _userStatusSupported = false;
|
||||
bool _userStatusEmojisSupported = false;
|
||||
|
||||
QPointer<JsonApiJob> _clearMessageJob {};
|
||||
QPointer<JsonApiJob> _setMessageJob {};
|
||||
QPointer<JsonApiJob> _setOnlineStatusJob {};
|
||||
QPointer<JsonApiJob> _getPredefinedStausesJob {};
|
||||
QPointer<JsonApiJob> _getUserStatusJob {};
|
||||
|
||||
UserStatus _userStatus;
|
||||
};
|
||||
}
|
121
src/libsync/userstatusconnector.cpp
Normal file
121
src/libsync/userstatusconnector.cpp
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#include "userstatusconnector.h"
|
||||
#include "theme.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
UserStatus::UserStatus() = default;
|
||||
|
||||
UserStatus::UserStatus(
|
||||
const QString &id, const QString &message, const QString &icon,
|
||||
OnlineStatus state, bool messagePredefined, const Optional<ClearAt> &clearAt)
|
||||
: _id(id)
|
||||
, _message(message)
|
||||
, _icon(icon)
|
||||
, _state(state)
|
||||
, _messagePredefined(messagePredefined)
|
||||
, _clearAt(clearAt)
|
||||
{
|
||||
}
|
||||
|
||||
QString UserStatus::id() const
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
QString UserStatus::message() const
|
||||
{
|
||||
return _message;
|
||||
}
|
||||
|
||||
QString UserStatus::icon() const
|
||||
{
|
||||
return _icon;
|
||||
}
|
||||
|
||||
auto UserStatus::state() const -> OnlineStatus
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
|
||||
bool UserStatus::messagePredefined() const
|
||||
{
|
||||
return _messagePredefined;
|
||||
}
|
||||
|
||||
QUrl UserStatus::stateIcon() const
|
||||
{
|
||||
switch (_state) {
|
||||
case UserStatus::OnlineStatus::Away:
|
||||
return Theme::instance()->statusAwayImageSource();
|
||||
|
||||
case UserStatus::OnlineStatus::DoNotDisturb:
|
||||
return Theme::instance()->statusDoNotDisturbImageSource();
|
||||
|
||||
case UserStatus::OnlineStatus::Invisible:
|
||||
case UserStatus::OnlineStatus::Offline:
|
||||
return Theme::instance()->statusInvisibleImageSource();
|
||||
|
||||
case UserStatus::OnlineStatus::Online:
|
||||
return Theme::instance()->statusOnlineImageSource();
|
||||
}
|
||||
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
Optional<ClearAt> UserStatus::clearAt() const
|
||||
{
|
||||
return _clearAt;
|
||||
}
|
||||
|
||||
void UserStatus::setId(const QString &id)
|
||||
{
|
||||
_id = id;
|
||||
}
|
||||
|
||||
void UserStatus::setMessage(const QString &message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
void UserStatus::setState(OnlineStatus state)
|
||||
{
|
||||
_state = state;
|
||||
}
|
||||
|
||||
void UserStatus::setIcon(const QString &icon)
|
||||
{
|
||||
_icon = icon;
|
||||
}
|
||||
|
||||
void UserStatus::setMessagePredefined(bool value)
|
||||
{
|
||||
_messagePredefined = value;
|
||||
}
|
||||
|
||||
void UserStatus::setClearAt(const Optional<ClearAt> &dateTime)
|
||||
{
|
||||
_clearAt = dateTime;
|
||||
}
|
||||
|
||||
|
||||
UserStatusConnector::UserStatusConnector(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
UserStatusConnector::~UserStatusConnector() = default;
|
||||
}
|
138
src/libsync/userstatusconnector.h
Normal file
138
src/libsync/userstatusconnector.h
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/result.h"
|
||||
#include "owncloudlib.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
#include <QUrl>
|
||||
#include <QDateTime>
|
||||
#include <QtGlobal>
|
||||
#include <QVariant>
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace OCC {
|
||||
|
||||
enum class OWNCLOUDSYNC_EXPORT ClearAtType {
|
||||
Period,
|
||||
EndOf,
|
||||
Timestamp
|
||||
};
|
||||
|
||||
// TODO: If we can use C++17 make it a std::variant
|
||||
struct OWNCLOUDSYNC_EXPORT ClearAt
|
||||
{
|
||||
ClearAtType _type = ClearAtType::Period;
|
||||
|
||||
quint64 _timestamp;
|
||||
int _period;
|
||||
QString _endof;
|
||||
};
|
||||
|
||||
class OWNCLOUDSYNC_EXPORT UserStatus
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(QString id MEMBER _id)
|
||||
Q_PROPERTY(QString message MEMBER _message)
|
||||
Q_PROPERTY(QString icon MEMBER _icon)
|
||||
Q_PROPERTY(OnlineStatus state MEMBER _state)
|
||||
|
||||
public:
|
||||
enum class OnlineStatus : quint8 {
|
||||
Online,
|
||||
DoNotDisturb,
|
||||
Away,
|
||||
Offline,
|
||||
Invisible
|
||||
};
|
||||
Q_ENUM(OnlineStatus);
|
||||
|
||||
UserStatus();
|
||||
|
||||
UserStatus(const QString &id, const QString &message, const QString &icon,
|
||||
OnlineStatus state, bool messagePredefined, const Optional<ClearAt> &clearAt = {});
|
||||
|
||||
Q_REQUIRED_RESULT QString id() const;
|
||||
Q_REQUIRED_RESULT QString message() const;
|
||||
Q_REQUIRED_RESULT QString icon() const;
|
||||
Q_REQUIRED_RESULT OnlineStatus state() const;
|
||||
Q_REQUIRED_RESULT Optional<ClearAt> clearAt() const;
|
||||
|
||||
void setId(const QString &id);
|
||||
void setMessage(const QString &message);
|
||||
void setState(OnlineStatus state);
|
||||
void setIcon(const QString &icon);
|
||||
void setMessagePredefined(bool value);
|
||||
void setClearAt(const Optional<ClearAt> &dateTime);
|
||||
|
||||
Q_REQUIRED_RESULT bool messagePredefined() const;
|
||||
|
||||
Q_REQUIRED_RESULT QUrl stateIcon() const;
|
||||
|
||||
private:
|
||||
QString _id;
|
||||
QString _message;
|
||||
QString _icon;
|
||||
OnlineStatus _state = OnlineStatus::Online;
|
||||
bool _messagePredefined;
|
||||
Optional<ClearAt> _clearAt;
|
||||
};
|
||||
|
||||
class OWNCLOUDSYNC_EXPORT UserStatusConnector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Error {
|
||||
CouldNotFetchUserStatus,
|
||||
CouldNotFetchPredefinedUserStatuses,
|
||||
UserStatusNotSupported,
|
||||
EmojisNotSupported,
|
||||
CouldNotSetUserStatus,
|
||||
CouldNotClearMessage
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
explicit UserStatusConnector(QObject *parent = nullptr);
|
||||
|
||||
~UserStatusConnector() override;
|
||||
|
||||
virtual void fetchUserStatus() = 0;
|
||||
|
||||
virtual void fetchPredefinedStatuses() = 0;
|
||||
|
||||
virtual void setUserStatus(const UserStatus &userStatus) = 0;
|
||||
|
||||
virtual void clearMessage() = 0;
|
||||
|
||||
virtual UserStatus userStatus() const = 0;
|
||||
|
||||
signals:
|
||||
void userStatusFetched(const UserStatus &userStatus);
|
||||
void predefinedStatusesFetched(const std::vector<UserStatus> &statuses);
|
||||
void userStatusSet();
|
||||
void messageCleared();
|
||||
void error(Error error);
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(OCC::UserStatusConnector *)
|
||||
Q_DECLARE_METATYPE(OCC::UserStatus)
|
|
@ -59,6 +59,7 @@ nextcloud_add_test(PushNotifications)
|
|||
nextcloud_add_test(Theme)
|
||||
nextcloud_add_test(IconUtils)
|
||||
nextcloud_add_test(NotificationCache)
|
||||
nextcloud_add_test(SetUserStatusDialog)
|
||||
|
||||
if( UNIX AND NOT APPLE )
|
||||
nextcloud_add_test(InotifyWatcher)
|
||||
|
|
|
@ -138,6 +138,82 @@ private slots:
|
|||
|
||||
QCOMPARE(capabilities.pushNotificationsWebSocketUrl(), websocketUrl);
|
||||
}
|
||||
|
||||
void testUserStatus_userStatusAvailable_returnTrue()
|
||||
{
|
||||
QVariantMap userStatusMap;
|
||||
userStatusMap["enabled"] = true;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["user_status"] = userStatusMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(capabilities.userStatus());
|
||||
}
|
||||
|
||||
void testUserStatus_userStatusNotAvailable_returnFalse()
|
||||
{
|
||||
QVariantMap userStatusMap;
|
||||
userStatusMap["enabled"] = false;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["user_status"] = userStatusMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(!capabilities.userStatus());
|
||||
}
|
||||
|
||||
void testUserStatus_userStatusNotInCapabilites_returnFalse()
|
||||
{
|
||||
QVariantMap capabilitiesMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(!capabilities.userStatus());
|
||||
}
|
||||
|
||||
void testUserStatusSupportsEmoji_supportsEmojiAvailable_returnTrue()
|
||||
{
|
||||
QVariantMap userStatusMap;
|
||||
userStatusMap["enabled"] = true;
|
||||
userStatusMap["supports_emoji"] = true;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["user_status"] = userStatusMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(capabilities.userStatus());
|
||||
}
|
||||
|
||||
void testUserStatusSupportsEmoji_supportsEmojiNotAvailable_returnFalse()
|
||||
{
|
||||
QVariantMap userStatusMap;
|
||||
userStatusMap["enabled"] = true;
|
||||
userStatusMap["supports_emoji"] = false;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["user_status"] = userStatusMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(!capabilities.userStatusSupportsEmoji());
|
||||
}
|
||||
|
||||
void testUserStatusSupportsEmoji_supportsEmojiNotInCapabilites_returnFalse()
|
||||
{
|
||||
QVariantMap userStatusMap;
|
||||
userStatusMap["enabled"] = true;
|
||||
|
||||
QVariantMap capabilitiesMap;
|
||||
capabilitiesMap["user_status"] = userStatusMap;
|
||||
|
||||
const OCC::Capabilities capabilities(capabilitiesMap);
|
||||
|
||||
QVERIFY(!capabilities.userStatusSupportsEmoji());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestCapabilities)
|
||||
|
|
|
@ -65,6 +65,20 @@ class TestPushNotifications : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testTryReconnect_capabilitesReportPushNotificationsAvailable_reconnectForEver()
|
||||
{
|
||||
FakeWebSocketServer fakeServer;
|
||||
auto account = FakeWebSocketServer::createAccount();
|
||||
account->setPushNotificationsReconnectInterval(0);
|
||||
|
||||
// Let if fail a few times
|
||||
QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
|
||||
QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
|
||||
|
||||
// Push notifications should try to reconnect
|
||||
QVERIFY(fakeServer.authenticateAccount(account));
|
||||
}
|
||||
|
||||
void testSetup_correctCredentials_authenticateAndEmitReady()
|
||||
{
|
||||
FakeWebSocketServer fakeServer;
|
||||
|
@ -272,20 +286,6 @@ private slots:
|
|||
QVERIFY(verifyCalledOnceWithAccount(*activitiesChangedSpy, account));
|
||||
}));
|
||||
}
|
||||
|
||||
void testTryReconnect_capabilitesReportPushNotificationsAvailable_reconnectForEver()
|
||||
{
|
||||
FakeWebSocketServer fakeServer;
|
||||
auto account = FakeWebSocketServer::createAccount();
|
||||
account->setPushNotificationsReconnectInterval(0);
|
||||
|
||||
// Let if fail a few times
|
||||
QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
|
||||
QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
|
||||
|
||||
// Push notifications should try to reconnect
|
||||
QVERIFY(fakeServer.authenticateAccount(account));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestPushNotifications)
|
||||
|
|
747
test/testsetuserstatusdialog.cpp
Normal file
747
test/testsetuserstatusdialog.cpp
Normal file
|
@ -0,0 +1,747 @@
|
|||
/*
|
||||
* Copyright (C) by Felix Weilbach <felix.weilbach@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.
|
||||
*/
|
||||
|
||||
#include "userstatusconnector.h"
|
||||
#include "userstatusselectormodel.h"
|
||||
|
||||
#include <QTest>
|
||||
#include <QSignalSpy>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class FakeUserStatusConnector : public OCC::UserStatusConnector
|
||||
{
|
||||
public:
|
||||
void fetchUserStatus() override
|
||||
{
|
||||
if (_couldNotFetchUserStatus) {
|
||||
emit error(Error::CouldNotFetchUserStatus);
|
||||
return;
|
||||
} else if (_userStatusNotSupported) {
|
||||
emit error(Error::UserStatusNotSupported);
|
||||
return;
|
||||
} else if (_emojisNotSupported) {
|
||||
emit error(Error::EmojisNotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
emit userStatusFetched(_userStatus);
|
||||
}
|
||||
|
||||
void fetchPredefinedStatuses() override
|
||||
{
|
||||
if (_couldNotFetchPredefinedUserStatuses) {
|
||||
emit error(Error::CouldNotFetchPredefinedUserStatuses);
|
||||
return;
|
||||
}
|
||||
emit predefinedStatusesFetched(_predefinedStatuses);
|
||||
}
|
||||
|
||||
void setUserStatus(const OCC::UserStatus &userStatus) override
|
||||
{
|
||||
if (_couldNotSetUserStatusMessage) {
|
||||
emit error(Error::CouldNotSetUserStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
_userStatusSetByCallerOfSetUserStatus = userStatus;
|
||||
emit UserStatusConnector::userStatusSet();
|
||||
}
|
||||
|
||||
void clearMessage() override
|
||||
{
|
||||
if (_couldNotClearUserStatusMessage) {
|
||||
emit error(Error::CouldNotClearMessage);
|
||||
} else {
|
||||
_isMessageCleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
OCC::UserStatus userStatus() const override
|
||||
{
|
||||
return {}; // Not implemented
|
||||
}
|
||||
|
||||
void setFakeUserStatus(const OCC::UserStatus &userStatus)
|
||||
{
|
||||
_userStatus = userStatus;
|
||||
}
|
||||
|
||||
void setFakePredefinedStatuses(
|
||||
const std::vector<OCC::UserStatus> &statuses)
|
||||
{
|
||||
_predefinedStatuses = statuses;
|
||||
}
|
||||
|
||||
OCC::UserStatus userStatusSetByCallerOfSetUserStatus() const { return _userStatusSetByCallerOfSetUserStatus; }
|
||||
|
||||
bool messageCleared() const { return _isMessageCleared; }
|
||||
|
||||
void setErrorCouldNotFetchPredefinedUserStatuses(bool value)
|
||||
{
|
||||
_couldNotFetchPredefinedUserStatuses = value;
|
||||
}
|
||||
|
||||
void setErrorCouldNotFetchUserStatus(bool value)
|
||||
{
|
||||
_couldNotFetchUserStatus = value;
|
||||
}
|
||||
|
||||
void setErrorCouldNotSetUserStatusMessage(bool value)
|
||||
{
|
||||
_couldNotSetUserStatusMessage = value;
|
||||
}
|
||||
|
||||
void setErrorUserStatusNotSupported(bool value)
|
||||
{
|
||||
_userStatusNotSupported = value;
|
||||
}
|
||||
|
||||
void setErrorEmojisNotSupported(bool value)
|
||||
{
|
||||
_emojisNotSupported = value;
|
||||
}
|
||||
|
||||
void setErrorCouldNotClearUserStatusMessage(bool value)
|
||||
{
|
||||
_couldNotClearUserStatusMessage = value;
|
||||
}
|
||||
|
||||
private:
|
||||
OCC::UserStatus _userStatusSetByCallerOfSetUserStatus;
|
||||
OCC::UserStatus _userStatus;
|
||||
std::vector<OCC::UserStatus> _predefinedStatuses;
|
||||
bool _isMessageCleared = false;
|
||||
bool _couldNotFetchPredefinedUserStatuses = false;
|
||||
bool _couldNotFetchUserStatus = false;
|
||||
bool _couldNotSetUserStatusMessage = false;
|
||||
bool _userStatusNotSupported = false;
|
||||
bool _emojisNotSupported = false;
|
||||
bool _couldNotClearUserStatusMessage = false;
|
||||
};
|
||||
|
||||
class FakeDateTimeProvider : public OCC::DateTimeProvider
|
||||
{
|
||||
public:
|
||||
void setCurrentDateTime(const QDateTime &dateTime) { _dateTime = dateTime; }
|
||||
|
||||
QDateTime currentDateTime() const override { return _dateTime; }
|
||||
|
||||
QDate currentDate() const override { return _dateTime.date(); }
|
||||
|
||||
private:
|
||||
QDateTime _dateTime;
|
||||
};
|
||||
|
||||
static std::vector<OCC::UserStatus>
|
||||
createFakePredefinedStatuses(const QDateTime ¤tTime)
|
||||
{
|
||||
std::vector<OCC::UserStatus> statuses;
|
||||
|
||||
const QString userStatusId("fake-id");
|
||||
const QString userStatusMessage("Predefined status");
|
||||
const QString userStatusIcon("🏖");
|
||||
const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
|
||||
const bool userStatusMessagePredefined(true);
|
||||
OCC::Optional<OCC::ClearAt> userStatusClearAt;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t();
|
||||
userStatusClearAt = clearAt;
|
||||
|
||||
statuses.emplace_back(userStatusId, userStatusMessage, userStatusIcon,
|
||||
userStatusState, userStatusMessagePredefined, userStatusClearAt);
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
static QDateTime createDateTime(int year = 2021, int month = 7, int day = 27,
|
||||
int hour = 12, int minute = 0, int second = 0)
|
||||
{
|
||||
QDate fakeDate(year, month, day);
|
||||
QTime fakeTime(hour, minute, second);
|
||||
QDateTime fakeDateTime;
|
||||
|
||||
fakeDateTime.setDate(fakeDate);
|
||||
fakeDateTime.setTime(fakeTime);
|
||||
|
||||
return fakeDateTime;
|
||||
}
|
||||
|
||||
class TestSetUserStatusDialog : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void testCtor_fetchStatusAndPredefinedStatuses()
|
||||
{
|
||||
const QDateTime currentDateTime(QDateTime::currentDateTime());
|
||||
|
||||
const QString userStatusId("fake-id");
|
||||
const QString userStatusMessage("Some status");
|
||||
const QString userStatusIcon("❤");
|
||||
const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::DoNotDisturb);
|
||||
const bool userStatusMessagePredefined(false);
|
||||
OCC::Optional<OCC::ClearAt> userStatusClearAt;
|
||||
{
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentDateTime.addDays(1).toTime_t();
|
||||
userStatusClearAt = clearAt;
|
||||
}
|
||||
|
||||
const OCC::UserStatus userStatus(userStatusId, userStatusMessage,
|
||||
userStatusIcon, userStatusState, userStatusMessagePredefined, userStatusClearAt);
|
||||
|
||||
const auto fakePredefinedStatuses = createFakePredefinedStatuses(createDateTime());
|
||||
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentDateTime);
|
||||
fakeUserStatusJob->setFakeUserStatus(userStatus);
|
||||
fakeUserStatusJob->setFakePredefinedStatuses(fakePredefinedStatuses);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob, std::move(fakeDateTimeProvider));
|
||||
|
||||
// Was user status set correctly?
|
||||
QCOMPARE(model.userStatusMessage(), userStatusMessage);
|
||||
QCOMPARE(model.userStatusEmoji(), userStatusIcon);
|
||||
QCOMPARE(model.onlineStatus(), userStatusState);
|
||||
QCOMPARE(model.clearAt(), tr("1 day"));
|
||||
|
||||
// Were predefined statuses fetched correctly?
|
||||
const auto predefinedStatusesCount = model.predefinedStatusesCount();
|
||||
QCOMPARE(predefinedStatusesCount, fakePredefinedStatuses.size());
|
||||
for (int i = 0; i < predefinedStatusesCount; ++i) {
|
||||
const auto predefinedStatus = model.predefinedStatus(i);
|
||||
QCOMPARE(predefinedStatus.id(),
|
||||
fakePredefinedStatuses[i].id());
|
||||
QCOMPARE(predefinedStatus.message(),
|
||||
fakePredefinedStatuses[i].message());
|
||||
QCOMPARE(predefinedStatus.icon(),
|
||||
fakePredefinedStatuses[i].icon());
|
||||
QCOMPARE(predefinedStatus.messagePredefined(),
|
||||
fakePredefinedStatuses[i].messagePredefined());
|
||||
}
|
||||
}
|
||||
|
||||
void testCtor_noStatusSet_showSensibleDefaults()
|
||||
{
|
||||
OCC::UserStatusSelectorModel model(nullptr, nullptr);
|
||||
|
||||
QCOMPARE(model.userStatusMessage(), "");
|
||||
QCOMPARE(model.userStatusEmoji(), "😀");
|
||||
QCOMPARE(model.clearAt(), tr("Don't clear"));
|
||||
}
|
||||
|
||||
void testCtor_fetchStatusButNoStatusSet_showSensibleDefaults()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setFakeUserStatus({ "", "", "",
|
||||
OCC::UserStatus::OnlineStatus::Offline, false, {} });
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.onlineStatus(), OCC::UserStatus::OnlineStatus::Online);
|
||||
QCOMPARE(model.userStatusMessage(), "");
|
||||
QCOMPARE(model.userStatusEmoji(), "😀");
|
||||
QCOMPARE(model.clearAt(), tr("Don't clear"));
|
||||
}
|
||||
|
||||
void testSetOnlineStatus_emitOnlineStatusChanged()
|
||||
{
|
||||
const OCC::UserStatus::OnlineStatus onlineStatus(OCC::UserStatus::OnlineStatus::Invisible);
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy onlineStatusChangedSpy(&model,
|
||||
&OCC::UserStatusSelectorModel::onlineStatusChanged);
|
||||
|
||||
model.setOnlineStatus(onlineStatus);
|
||||
|
||||
QCOMPARE(onlineStatusChangedSpy.count(), 1);
|
||||
}
|
||||
|
||||
void testSetUserStatus_setCustomMessage_userStatusSetCorrect()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
|
||||
|
||||
const QString userStatusMessage("Some status");
|
||||
const QString userStatusIcon("❤");
|
||||
const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
|
||||
|
||||
model.setOnlineStatus(userStatusState);
|
||||
model.setUserStatusMessage(userStatusMessage);
|
||||
model.setUserStatusEmoji(userStatusIcon);
|
||||
model.setClearAt(1);
|
||||
|
||||
model.setUserStatus();
|
||||
QCOMPARE(finishedSpy.count(), 1);
|
||||
|
||||
const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
|
||||
QCOMPARE(userStatusSet.icon(), userStatusIcon);
|
||||
QCOMPARE(userStatusSet.message(), userStatusMessage);
|
||||
QCOMPARE(userStatusSet.state(), userStatusState);
|
||||
QCOMPARE(userStatusSet.messagePredefined(), false);
|
||||
const auto clearAt = userStatusSet.clearAt();
|
||||
QVERIFY(clearAt.isValid());
|
||||
QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
|
||||
QCOMPARE(clearAt->_period, 60 * 30);
|
||||
}
|
||||
|
||||
void testSetUserStatusMessage_predefinedStatusWasSet_userStatusSetCorrect()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setFakePredefinedStatuses(createFakePredefinedStatuses(createDateTime()));
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
model.setPredefinedStatus(0);
|
||||
QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
|
||||
|
||||
const QString userStatusMessage("Some status");
|
||||
const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
|
||||
|
||||
model.setOnlineStatus(userStatusState);
|
||||
model.setUserStatusMessage(userStatusMessage);
|
||||
model.setClearAt(1);
|
||||
|
||||
model.setUserStatus();
|
||||
QCOMPARE(finishedSpy.count(), 1);
|
||||
|
||||
const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
|
||||
QCOMPARE(userStatusSet.message(), userStatusMessage);
|
||||
QCOMPARE(userStatusSet.state(), userStatusState);
|
||||
QCOMPARE(userStatusSet.messagePredefined(), false);
|
||||
const auto clearAt = userStatusSet.clearAt();
|
||||
QVERIFY(clearAt.isValid());
|
||||
QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
|
||||
QCOMPARE(clearAt->_period, 60 * 30);
|
||||
}
|
||||
|
||||
void testSetUserStatusEmoji_predefinedStatusWasSet_userStatusSetCorrect()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setFakePredefinedStatuses(createFakePredefinedStatuses(createDateTime()));
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
model.setPredefinedStatus(0);
|
||||
QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
|
||||
|
||||
const QString userStatusIcon("❤");
|
||||
const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
|
||||
|
||||
model.setOnlineStatus(userStatusState);
|
||||
model.setUserStatusEmoji(userStatusIcon);
|
||||
model.setClearAt(1);
|
||||
|
||||
model.setUserStatus();
|
||||
QCOMPARE(finishedSpy.count(), 1);
|
||||
|
||||
const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
|
||||
QCOMPARE(userStatusSet.icon(), userStatusIcon);
|
||||
QCOMPARE(userStatusSet.state(), userStatusState);
|
||||
QCOMPARE(userStatusSet.messagePredefined(), false);
|
||||
const auto clearAt = userStatusSet.clearAt();
|
||||
QVERIFY(clearAt.isValid());
|
||||
QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
|
||||
QCOMPARE(clearAt->_period, 60 * 30);
|
||||
}
|
||||
|
||||
void testSetPredefinedStatus_emitUserStatusChangedAndSetUserStatus()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
const auto currentTime = createDateTime();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
const auto fakePredefinedStatuses = createFakePredefinedStatuses(currentTime);
|
||||
fakeUserStatusJob->setFakePredefinedStatuses(fakePredefinedStatuses);
|
||||
OCC::UserStatusSelectorModel model(std::move(fakeUserStatusJob),
|
||||
std::move(fakeDateTimeProvider));
|
||||
|
||||
QSignalSpy userStatusChangedSpy(&model,
|
||||
&OCC::UserStatusSelectorModel::userStatusChanged);
|
||||
QSignalSpy clearAtChangedSpy(&model,
|
||||
&OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto fakePredefinedUserStatusIndex = 0;
|
||||
model.setPredefinedStatus(fakePredefinedUserStatusIndex);
|
||||
|
||||
QCOMPARE(userStatusChangedSpy.count(), 1);
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
|
||||
// Was user status set correctly?
|
||||
const auto fakePredefinedUserStatus = fakePredefinedStatuses[fakePredefinedUserStatusIndex];
|
||||
QCOMPARE(model.userStatusMessage(), fakePredefinedUserStatus.message());
|
||||
QCOMPARE(model.userStatusEmoji(), fakePredefinedUserStatus.icon());
|
||||
QCOMPARE(model.onlineStatus(), fakePredefinedUserStatus.state());
|
||||
QCOMPARE(model.clearAt(), tr("1 hour"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage0_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 0;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("Don't clear"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage1_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 1;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("30 minutes"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage2_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 2;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("1 hour"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage3_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 3;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("4 hours"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage4_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 4;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("Today"));
|
||||
}
|
||||
|
||||
void testSetClear_setClearAtStage5_emitClearAtChangedAndClearAtSet()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
|
||||
|
||||
const auto clearAtIndex = 5;
|
||||
model.setClearAt(clearAtIndex);
|
||||
|
||||
QCOMPARE(clearAtChangedSpy.count(), 1);
|
||||
QCOMPARE(model.clearAt(), tr("This week"));
|
||||
}
|
||||
|
||||
void testClearAtStages()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("Don't clear"));
|
||||
const auto clearAtValues = model.clearAtValues();
|
||||
QCOMPARE(clearAtValues.count(), 6);
|
||||
|
||||
QCOMPARE(clearAtValues[0], tr("Don't clear"));
|
||||
QCOMPARE(clearAtValues[1], tr("30 minutes"));
|
||||
QCOMPARE(clearAtValues[2], tr("1 hour"));
|
||||
QCOMPARE(clearAtValues[3], tr("4 hours"));
|
||||
QCOMPARE(clearAtValues[4], tr("Today"));
|
||||
QCOMPARE(clearAtValues[5], tr("This week"));
|
||||
}
|
||||
|
||||
void testClearAt_clearAtTimestamp()
|
||||
{
|
||||
const auto currentTime = createDateTime();
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(30).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("Less than a minute"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(60).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("1 minute"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(60 * 30).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("30 minutes"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("1 hour"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addSecs(60 * 60 * 4).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("4 hours"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addDays(1).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("1 day"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Timestamp;
|
||||
clearAt._timestamp = currentTime.addDays(7).toTime_t();
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
|
||||
fakeDateTimeProvider->setCurrentDateTime(currentTime);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("7 days"));
|
||||
}
|
||||
}
|
||||
|
||||
void testClearAt_clearAtEndOf()
|
||||
{
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::EndOf;
|
||||
clearAt._endof = "day";
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus);
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("Today"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::EndOf;
|
||||
clearAt._endof = "week";
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus);
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("This week"));
|
||||
}
|
||||
}
|
||||
|
||||
void testClearAt_clearAtAfterPeriod()
|
||||
{
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Period;
|
||||
clearAt._period = 60 * 30;
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus);
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("30 minutes"));
|
||||
}
|
||||
|
||||
{
|
||||
OCC::UserStatus userStatus;
|
||||
OCC::ClearAt clearAt;
|
||||
clearAt._type = OCC::ClearAtType::Period;
|
||||
clearAt._period = 60 * 60;
|
||||
userStatus.setClearAt(clearAt);
|
||||
|
||||
OCC::UserStatusSelectorModel model(userStatus);
|
||||
|
||||
QCOMPARE(model.clearAt(), tr("1 hour"));
|
||||
}
|
||||
}
|
||||
|
||||
void testClearUserStatus()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
model.clearUserStatus();
|
||||
|
||||
QVERIFY(fakeUserStatusJob->messageCleared());
|
||||
}
|
||||
|
||||
void testError_couldNotFetchPredefinedStatuses_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorCouldNotFetchPredefinedUserStatuses(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("Could not fetch predefined statuses. Make sure you are connected to the server."));
|
||||
}
|
||||
|
||||
void testError_couldNotFetchUserStatus_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorCouldNotFetchUserStatus(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("Could not fetch user status. Make sure you are connected to the server."));
|
||||
}
|
||||
|
||||
void testError_userStatusNotSupported_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorUserStatusNotSupported(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("User status feature is not supported. You will not be able to set your user status."));
|
||||
}
|
||||
|
||||
void testError_couldSetUserStatus_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
model.setUserStatus();
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("Could not set user status. Make sure you are connected to the server."));
|
||||
}
|
||||
|
||||
void testError_emojisNotSupported_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorEmojisNotSupported(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("Emojis feature is not supported. Some user status functionality may not work."));
|
||||
}
|
||||
|
||||
void testError_couldNotClearMessage_emitError()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
fakeUserStatusJob->setErrorCouldNotClearUserStatusMessage(true);
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
model.clearUserStatus();
|
||||
|
||||
QCOMPARE(model.errorMessage(),
|
||||
tr("Could not clear user status message. Make sure you are connected to the server."));
|
||||
}
|
||||
|
||||
void testError_setUserStatus_clearErrorMessage()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
|
||||
model.setUserStatus();
|
||||
QVERIFY(!model.errorMessage().isEmpty());
|
||||
fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(false);
|
||||
model.setUserStatus();
|
||||
QVERIFY(model.errorMessage().isEmpty());
|
||||
}
|
||||
|
||||
void testError_clearUserStatus_clearErrorMessage()
|
||||
{
|
||||
auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
|
||||
OCC::UserStatusSelectorModel model(fakeUserStatusJob);
|
||||
|
||||
fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
|
||||
model.setUserStatus();
|
||||
QVERIFY(!model.errorMessage().isEmpty());
|
||||
fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(false);
|
||||
model.clearUserStatus();
|
||||
QVERIFY(model.errorMessage().isEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TestSetUserStatusDialog)
|
||||
#include "testsetuserstatusdialog.moc"
|
Loading…
Reference in a new issue