mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-29 12:19:03 +03:00
Merge pull request #4189 from nextcloud/feature/activity_thumbnails
Add thumbnails for files in the activity view
This commit is contained in:
commit
6aefe8f2e3
26 changed files with 835 additions and 261 deletions
|
@ -195,10 +195,10 @@ set(client_SRCS
|
||||||
tray/activitylistmodel.h
|
tray/activitylistmodel.h
|
||||||
tray/activitylistmodel.cpp
|
tray/activitylistmodel.cpp
|
||||||
tray/unifiedsearchresult.h
|
tray/unifiedsearchresult.h
|
||||||
|
tray/asyncimageresponse.cpp
|
||||||
tray/unifiedsearchresult.cpp
|
tray/unifiedsearchresult.cpp
|
||||||
tray/unifiedsearchresultimageprovider.h
|
|
||||||
tray/unifiedsearchresultimageprovider.cpp
|
|
||||||
tray/unifiedsearchresultslistmodel.h
|
tray/unifiedsearchresultslistmodel.h
|
||||||
|
tray/trayimageprovider.cpp
|
||||||
tray/unifiedsearchresultslistmodel.cpp
|
tray/unifiedsearchresultslistmodel.cpp
|
||||||
tray/usermodel.h
|
tray/usermodel.h
|
||||||
tray/usermodel.cpp
|
tray/usermodel.cpp
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
#include "tray/svgimageprovider.h"
|
#include "tray/svgimageprovider.h"
|
||||||
#include "tray/usermodel.h"
|
#include "tray/usermodel.h"
|
||||||
#include "wheelhandler.h"
|
#include "wheelhandler.h"
|
||||||
#include "tray/unifiedsearchresultimageprovider.h"
|
#include "tray/trayimageprovider.h"
|
||||||
#include "configfile.h"
|
#include "configfile.h"
|
||||||
#include "accessmanager.h"
|
#include "accessmanager.h"
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine)
|
||||||
_trayEngine->addImportPath("qrc:/qml/theme");
|
_trayEngine->addImportPath("qrc:/qml/theme");
|
||||||
_trayEngine->addImageProvider("avatars", new ImageProvider);
|
_trayEngine->addImageProvider("avatars", new ImageProvider);
|
||||||
_trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider);
|
_trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider);
|
||||||
_trayEngine->addImageProvider(QLatin1String("unified-search-result-icon"), new UnifiedSearchResultImageProvider);
|
_trayEngine->addImageProvider(QLatin1String("tray-image-provider"), new TrayImageProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
Systray::Systray()
|
Systray::Systray()
|
||||||
|
@ -513,7 +513,11 @@ AccessManagerFactory::AccessManagerFactory()
|
||||||
|
|
||||||
QNetworkAccessManager* AccessManagerFactory::create(QObject *parent)
|
QNetworkAccessManager* AccessManagerFactory::create(QObject *parent)
|
||||||
{
|
{
|
||||||
return new AccessManager(parent);
|
const auto am = new AccessManager(parent);
|
||||||
|
const auto diskCache = new QNetworkDiskCache(am);
|
||||||
|
diskCache->setCacheDirectory("cacheDir");
|
||||||
|
am->setCache(diskCache);
|
||||||
|
return am;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace OCC
|
} // namespace OCC
|
||||||
|
|
|
@ -38,8 +38,8 @@ MouseArea {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.left: root.left
|
anchors.left: root.left
|
||||||
anchors.right: root.right
|
anchors.right: root.right
|
||||||
anchors.leftMargin: 15
|
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 10
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.2
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
import QtGraphicalEffects 1.15
|
||||||
import com.nextcloud.desktopclient 1.0
|
import com.nextcloud.desktopclient 1.0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -19,19 +20,66 @@ RowLayout {
|
||||||
signal dismissButtonClicked()
|
signal dismissButtonClicked()
|
||||||
signal shareButtonClicked()
|
signal shareButtonClicked()
|
||||||
|
|
||||||
spacing: 10
|
spacing: Style.trayHorizontalMargin
|
||||||
|
|
||||||
Image {
|
|
||||||
id: activityIcon
|
|
||||||
|
|
||||||
|
Item {
|
||||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||||
Layout.preferredWidth: 32
|
Layout.preferredWidth: Style.trayListItemIconSize
|
||||||
Layout.preferredHeight: 32
|
Layout.preferredHeight: Style.trayListItemIconSize
|
||||||
|
|
||||||
verticalAlignment: Qt.AlignCenter
|
Loader {
|
||||||
source: icon
|
id: thumbnailImageLoader
|
||||||
sourceSize.height: 64
|
anchors.fill: parent
|
||||||
sourceSize.width: 64
|
active: model.thumbnail !== undefined
|
||||||
|
|
||||||
|
sourceComponent: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: thumbnailImage
|
||||||
|
width: model.thumbnail.isMimeTypeIcon ? parent.width * 0.85 : parent.width * 0.8
|
||||||
|
height: model.thumbnail.isMimeTypeIcon ? parent.height * 0.85 : parent.height * 0.8
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.left: parent.left
|
||||||
|
cache: true
|
||||||
|
source: model.thumbnail.source
|
||||||
|
visible: false
|
||||||
|
sourceSize.height: 64
|
||||||
|
sourceSize.width: 64
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: mask
|
||||||
|
color: "white"
|
||||||
|
radius: 3
|
||||||
|
anchors.fill: thumbnailImage
|
||||||
|
visible: false
|
||||||
|
width: thumbnailImage.paintedWidth
|
||||||
|
height: thumbnailImage.paintedHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
OpacityMask {
|
||||||
|
anchors.fill: thumbnailImage
|
||||||
|
source: thumbnailImage
|
||||||
|
maskSource: mask
|
||||||
|
visible: model.thumbnail !== undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: activityIcon
|
||||||
|
width: model.thumbnail !== undefined ? parent.width * 0.5 : parent.width * 0.85
|
||||||
|
height: model.thumbnail !== undefined ? parent.height * 0.5 : parent.height * 0.85
|
||||||
|
anchors.verticalCenter: if(model.thumbnail === undefined) parent.verticalCenter
|
||||||
|
anchors.left: if(model.thumbnail === undefined) parent.left
|
||||||
|
anchors.right: if(model.thumbnail !== undefined) parent.right
|
||||||
|
anchors.bottom: if(model.thumbnail !== undefined) parent.bottom
|
||||||
|
cache: true
|
||||||
|
source: icon
|
||||||
|
sourceSize.height: 64
|
||||||
|
sourceSize.width: 64
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|
|
@ -11,7 +11,7 @@ RowLayout {
|
||||||
|
|
||||||
property alias model: syncStatus
|
property alias model: syncStatus
|
||||||
|
|
||||||
spacing: 0
|
spacing: Style.trayHorizontalMargin
|
||||||
|
|
||||||
NC.SyncStatusSummary {
|
NC.SyncStatusSummary {
|
||||||
id: syncStatus
|
id: syncStatus
|
||||||
|
@ -19,15 +19,18 @@ RowLayout {
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: syncIcon
|
id: syncIcon
|
||||||
|
Layout.preferredWidth: Style.trayListItemIconSize * 0.85
|
||||||
|
Layout.preferredHeight: Style.trayListItemIconSize * 0.85
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
|
Layout.rightMargin: Style.trayListItemIconSize * 0.15
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: Style.trayHorizontalMargin
|
||||||
|
|
||||||
source: syncStatus.syncIcon
|
source: syncStatus.syncIcon
|
||||||
sourceSize.width: 32
|
sourceSize.width: 64
|
||||||
sourceSize.height: 32
|
sourceSize.height: 64
|
||||||
rotation: syncStatus.syncing ? 0 : 0
|
rotation: syncStatus.syncing ? 0 : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,8 +48,7 @@ RowLayout {
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.topMargin: 8
|
Layout.topMargin: 8
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: Style.trayHorizontalMargin
|
||||||
Layout.leftMargin: 10
|
|
||||||
Layout.bottomMargin: 8
|
Layout.bottomMargin: 8
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
@ -65,7 +67,7 @@ RowLayout {
|
||||||
Loader {
|
Loader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
active: syncStatus.syncing;
|
active: syncStatus.syncing
|
||||||
visible: syncStatus.syncing
|
visible: syncStatus.syncing
|
||||||
|
|
||||||
sourceComponent: ProgressBar {
|
sourceComponent: ProgressBar {
|
||||||
|
|
|
@ -13,15 +13,15 @@ TextField {
|
||||||
|
|
||||||
readonly property color textFieldIconsColor: Style.menuBorder
|
readonly property color textFieldIconsColor: Style.menuBorder
|
||||||
|
|
||||||
readonly property int textFieldIconsOffset: 10
|
readonly property int textFieldIconsOffset: Style.trayHorizontalMargin
|
||||||
|
|
||||||
readonly property double textFieldIconsScaleFactor: 0.6
|
readonly property double textFieldIconsScaleFactor: 0.6
|
||||||
|
|
||||||
readonly property int textFieldHorizontalPaddingOffset: 14
|
readonly property int textFieldHorizontalPaddingOffset: Style.trayHorizontalMargin
|
||||||
|
|
||||||
signal clearText()
|
signal clearText()
|
||||||
|
|
||||||
leftPadding: trayWindowUnifiedSearchTextFieldSearchIcon.width + trayWindowUnifiedSearchTextFieldSearchIcon.anchors.leftMargin + textFieldHorizontalPaddingOffset
|
leftPadding: trayWindowUnifiedSearchTextFieldSearchIcon.width + trayWindowUnifiedSearchTextFieldSearchIcon.anchors.leftMargin + textFieldHorizontalPaddingOffset - 1
|
||||||
rightPadding: trayWindowUnifiedSearchTextFieldClearTextButton.width + trayWindowUnifiedSearchTextFieldClearTextButton.anchors.rightMargin + textFieldHorizontalPaddingOffset
|
rightPadding: trayWindowUnifiedSearchTextFieldClearTextButton.width + trayWindowUnifiedSearchTextFieldClearTextButton.anchors.rightMargin + textFieldHorizontalPaddingOffset
|
||||||
|
|
||||||
placeholderText: qsTr("Search files, messages, events …")
|
placeholderText: qsTr("Search files, messages, events …")
|
||||||
|
@ -36,6 +36,9 @@ TextField {
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: trayWindowUnifiedSearchTextFieldSearchIcon
|
id: trayWindowUnifiedSearchTextFieldSearchIcon
|
||||||
|
width: Style.trayListItemIconSize - anchors.leftMargin
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
horizontalAlignment: Image.AlignLeft
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
|
|
|
@ -39,7 +39,7 @@ RowLayout {
|
||||||
id: unifiedSearchResultThumbnail
|
id: unifiedSearchResultThumbnail
|
||||||
visible: false
|
visible: false
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
source: "image://unified-search-result-icon/" + icons
|
source: "image://tray-image-provider/" + icons
|
||||||
cache: true
|
cache: true
|
||||||
sourceSize.width: imageData.width
|
sourceSize.width: imageData.width
|
||||||
sourceSize.height: imageData.height
|
sourceSize.height: imageData.height
|
||||||
|
|
|
@ -22,8 +22,8 @@ Window {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint
|
flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint
|
||||||
|
|
||||||
|
|
||||||
property int fileActivityDialogObjectId: -1
|
property int fileActivityDialogObjectId: -1
|
||||||
|
|
||||||
readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth
|
readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth
|
||||||
|
|
||||||
function openFileActivityDialog(objectName, objectId) {
|
function openFileActivityDialog(objectName, objectId) {
|
||||||
|
@ -345,7 +345,7 @@ Window {
|
||||||
Image {
|
Image {
|
||||||
id: currentAccountAvatar
|
id: currentAccountAvatar
|
||||||
|
|
||||||
Layout.leftMargin: 8
|
Layout.leftMargin: Style.trayHorizontalMargin
|
||||||
verticalAlignment: Qt.AlignCenter
|
verticalAlignment: Qt.AlignCenter
|
||||||
cache: false
|
cache: false
|
||||||
source: UserModel.currentUser.avatar != "" ? UserModel.currentUser.avatar : "image://avatars/fallbackWhite"
|
source: UserModel.currentUser.avatar != "" ? UserModel.currentUser.avatar : "image://avatars/fallbackWhite"
|
||||||
|
@ -601,9 +601,9 @@ Window {
|
||||||
left: trayWindowBackground.left
|
left: trayWindowBackground.left
|
||||||
right: trayWindowBackground.right
|
right: trayWindowBackground.right
|
||||||
|
|
||||||
margins: {
|
topMargin: Style.trayHorizontalMargin + controlRoot.padding
|
||||||
top: 10
|
leftMargin: Style.trayHorizontalMargin + controlRoot.padding
|
||||||
}
|
rightMargin: Style.trayHorizontalMargin + controlRoot.padding
|
||||||
}
|
}
|
||||||
|
|
||||||
text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm
|
text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm
|
||||||
|
@ -623,7 +623,7 @@ Window {
|
||||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||||
anchors.left: trayWindowBackground.left
|
anchors.left: trayWindowBackground.left
|
||||||
anchors.right: trayWindowBackground.right
|
anchors.right: trayWindowBackground.right
|
||||||
anchors.margins: 10
|
anchors.margins: Style.trayHorizontalMargin
|
||||||
}
|
}
|
||||||
|
|
||||||
UnifiedSearchResultNothingFound {
|
UnifiedSearchResultNothingFound {
|
||||||
|
@ -632,7 +632,7 @@ Window {
|
||||||
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
anchors.top: trayWindowUnifiedSearchInputContainer.bottom
|
||||||
anchors.left: trayWindowBackground.left
|
anchors.left: trayWindowBackground.left
|
||||||
anchors.right: trayWindowBackground.right
|
anchors.right: trayWindowBackground.right
|
||||||
anchors.topMargin: 10
|
anchors.topMargin: Style.trayHorizontalMargin
|
||||||
|
|
||||||
text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm
|
text: UserModel.currentUser.unifiedSearchResultsListModel.searchTerm
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
|
|
||||||
#include "activitydata.h"
|
#include "activitydata.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
@ -44,4 +45,99 @@ ActivityLink ActivityLink::createFomJsonObject(const QJsonObject &obj)
|
||||||
|
|
||||||
return activityLink;
|
return activityLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPtr account)
|
||||||
|
{
|
||||||
|
const auto activityUser = json.value(QStringLiteral("user")).toString();
|
||||||
|
|
||||||
|
Activity activity;
|
||||||
|
activity._type = Activity::ActivityType;
|
||||||
|
activity._objectType = json.value(QStringLiteral("object_type")).toString();
|
||||||
|
activity._objectId = json.value(QStringLiteral("object_id")).toInt();
|
||||||
|
activity._objectName = json.value(QStringLiteral("object_name")).toString();
|
||||||
|
activity._id = json.value(QStringLiteral("activity_id")).toInt();
|
||||||
|
activity._fileAction = json.value(QStringLiteral("type")).toString();
|
||||||
|
activity._accName = account->displayName();
|
||||||
|
activity._subject = json.value(QStringLiteral("subject")).toString();
|
||||||
|
activity._message = json.value(QStringLiteral("message")).toString();
|
||||||
|
activity._file = json.value(QStringLiteral("object_name")).toString();
|
||||||
|
activity._link = QUrl(json.value(QStringLiteral("link")).toString());
|
||||||
|
activity._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate);
|
||||||
|
activity._icon = json.value(QStringLiteral("icon")).toString();
|
||||||
|
activity._isCurrentUserFileActivity = activity._objectType == QStringLiteral("files") && activityUser == account->davUser();
|
||||||
|
|
||||||
|
auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
|
||||||
|
|
||||||
|
if(richSubjectData.size() > 1) {
|
||||||
|
activity._subjectRich = richSubjectData[0].toString();
|
||||||
|
auto parameters = richSubjectData[1].toObject();
|
||||||
|
const QRegularExpression subjectRichParameterRe(QStringLiteral("({[a-zA-Z0-9]*})"));
|
||||||
|
const QRegularExpression subjectRichParameterBracesRe(QStringLiteral("[{}]"));
|
||||||
|
|
||||||
|
for (auto i = parameters.begin(); i != parameters.end(); ++i) {
|
||||||
|
const auto parameterJsonObject = i.value().toObject();
|
||||||
|
|
||||||
|
activity._subjectRichParameters[i.key()] = Activity::RichSubjectParameter {
|
||||||
|
parameterJsonObject.value(QStringLiteral("type")).toString(),
|
||||||
|
parameterJsonObject.value(QStringLiteral("id")).toString(),
|
||||||
|
parameterJsonObject.value(QStringLiteral("name")).toString(),
|
||||||
|
parameterJsonObject.contains(QStringLiteral("path")) ? parameterJsonObject.value(QStringLiteral("path")).toString() : QString(),
|
||||||
|
parameterJsonObject.contains(QStringLiteral("link")) ? QUrl(parameterJsonObject.value(QStringLiteral("link")).toString()) : QUrl(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto displayString = activity._subjectRich;
|
||||||
|
auto subjectRichParameterMatch = subjectRichParameterRe.globalMatch(displayString);
|
||||||
|
|
||||||
|
while (subjectRichParameterMatch.hasNext()) {
|
||||||
|
const auto match = subjectRichParameterMatch.next();
|
||||||
|
auto word = match.captured(1);
|
||||||
|
word.remove(subjectRichParameterBracesRe);
|
||||||
|
|
||||||
|
Q_ASSERT(activity._subjectRichParameters.contains(word));
|
||||||
|
displayString = displayString.replace(match.captured(1), activity._subjectRichParameters[word].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
activity._subjectDisplay = displayString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto previewsData = json.value(QStringLiteral("previews")).toArray();
|
||||||
|
|
||||||
|
for(const auto preview : previewsData) {
|
||||||
|
const auto jsonPreviewData = preview.toObject();
|
||||||
|
|
||||||
|
PreviewData data;
|
||||||
|
data._link = jsonPreviewData.value(QStringLiteral("link")).toString();
|
||||||
|
data._mimeType = jsonPreviewData.value(QStringLiteral("mimeType")).toString();
|
||||||
|
data._fileId = jsonPreviewData.value(QStringLiteral("fileId")).toInt();
|
||||||
|
data._view = jsonPreviewData.value(QStringLiteral("view")).toString();
|
||||||
|
data._filename = jsonPreviewData.value(QStringLiteral("filename")).toString();
|
||||||
|
|
||||||
|
if(data._mimeType.contains(QStringLiteral("text/"))) {
|
||||||
|
data._source = account->url().toString() + QStringLiteral("/index.php/apps/theming/img/core/filetypes/text.svg");
|
||||||
|
data._isMimeTypeIcon = true;
|
||||||
|
} else if (data._mimeType.contains(QStringLiteral("/pdf"))) {
|
||||||
|
data._source = account->url().toString() + QStringLiteral("/index.php/apps/theming/img/core/filetypes/application-pdf.svg");
|
||||||
|
data._isMimeTypeIcon = true;
|
||||||
|
} else {
|
||||||
|
data._source = jsonPreviewData.value(QStringLiteral("source")).toString();
|
||||||
|
data._isMimeTypeIcon = jsonPreviewData.value(QStringLiteral("isMimeTypeIcon")).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
activity._previews.append(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!previewsData.isEmpty()) {
|
||||||
|
if(activity._icon.contains(QStringLiteral("add-color.svg"))) {
|
||||||
|
activity._icon = "qrc:///client/theme/colored/add-bordered.svg";
|
||||||
|
} else if(activity._icon.contains(QStringLiteral("delete-color.svg"))) {
|
||||||
|
activity._icon = "qrc:///client/theme/colored/delete-bordered.svg";
|
||||||
|
} else if(activity._icon.contains(QStringLiteral("change.svg"))) {
|
||||||
|
activity._icon = "qrc:///client/theme/colored/change-bordered.svg";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,10 @@
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "syncfileitem.h"
|
||||||
|
#include "folder.h"
|
||||||
|
#include "account.h"
|
||||||
|
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
/**
|
/**
|
||||||
* @brief The ActivityLink class describes actions of an activity
|
* @brief The ActivityLink class describes actions of an activity
|
||||||
|
@ -49,6 +53,32 @@ public:
|
||||||
bool _primary;
|
bool _primary;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The PreviewData class describes the data about a file's preview.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class PreviewData
|
||||||
|
{
|
||||||
|
Q_GADGET
|
||||||
|
|
||||||
|
Q_PROPERTY(QString source MEMBER _source)
|
||||||
|
Q_PROPERTY(QString link MEMBER _link)
|
||||||
|
Q_PROPERTY(QString mimeType MEMBER _mimeType)
|
||||||
|
Q_PROPERTY(int fileId MEMBER _fileId)
|
||||||
|
Q_PROPERTY(QString view MEMBER _view)
|
||||||
|
Q_PROPERTY(bool isMimeTypeIcon MEMBER _isMimeTypeIcon)
|
||||||
|
Q_PROPERTY(QString filename MEMBER _filename)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString _source;
|
||||||
|
QString _link;
|
||||||
|
QString _mimeType;
|
||||||
|
int _fileId;
|
||||||
|
QString _view;
|
||||||
|
bool _isMimeTypeIcon;
|
||||||
|
QString _filename;
|
||||||
|
};
|
||||||
|
|
||||||
/* ==================================================================== */
|
/* ==================================================================== */
|
||||||
/**
|
/**
|
||||||
* @brief Activity Structure
|
* @brief Activity Structure
|
||||||
|
@ -69,6 +99,8 @@ public:
|
||||||
SyncFileItemType
|
SyncFileItemType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static Activity fromActivityJson(const QJsonObject json, const AccountPtr account);
|
||||||
|
|
||||||
struct RichSubjectParameter {
|
struct RichSubjectParameter {
|
||||||
QString type; // Required
|
QString type; // Required
|
||||||
QString id; // Required
|
QString id; // Required
|
||||||
|
@ -97,6 +129,7 @@ public:
|
||||||
QString _accName;
|
QString _accName;
|
||||||
QString _icon;
|
QString _icon;
|
||||||
bool _isCurrentUserFileActivity = false;
|
bool _isCurrentUserFileActivity = false;
|
||||||
|
QVector<PreviewData> _previews;
|
||||||
|
|
||||||
// Stores information about the error
|
// Stores information about the error
|
||||||
int _status;
|
int _status;
|
||||||
|
@ -127,5 +160,6 @@ using ActivityList = QList<Activity>;
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(OCC::Activity::Type)
|
Q_DECLARE_METATYPE(OCC::Activity::Type)
|
||||||
Q_DECLARE_METATYPE(OCC::ActivityLink)
|
Q_DECLARE_METATYPE(OCC::ActivityLink)
|
||||||
|
Q_DECLARE_METATYPE(OCC::PreviewData)
|
||||||
|
|
||||||
#endif // ACTIVITYDATA_H
|
#endif // ACTIVITYDATA_H
|
||||||
|
|
|
@ -74,6 +74,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
|
||||||
roles[DisplayActions] = "displayActions";
|
roles[DisplayActions] = "displayActions";
|
||||||
roles[ShareableRole] = "isShareable";
|
roles[ShareableRole] = "isShareable";
|
||||||
roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
|
roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
|
||||||
|
roles[ThumbnailRole] = "thumbnail";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +176,18 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||||
return displayPath == "." || displayPath == "/" ? QString() : displayPath;
|
return displayPath == "." || displayPath == "/" ? QString() : displayPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto generatePreviewMap = [](const PreviewData &preview) {
|
||||||
|
return(QVariantMap {
|
||||||
|
{QStringLiteral("source"), QStringLiteral("image://tray-image-provider/").append(preview._source)},
|
||||||
|
{QStringLiteral("link"), preview._link},
|
||||||
|
{QStringLiteral("mimeType"), preview._mimeType},
|
||||||
|
{QStringLiteral("fileId"), preview._fileId},
|
||||||
|
{QStringLiteral("view"), preview._view},
|
||||||
|
{QStringLiteral("isMimeTypeIcon"), preview._isMimeTypeIcon},
|
||||||
|
{QStringLiteral("filename"), preview._filename},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case DisplayPathRole:
|
case DisplayPathRole:
|
||||||
return getDisplayPath();
|
return getDisplayPath();
|
||||||
|
@ -220,11 +233,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||||
} else {
|
} else {
|
||||||
// File sync successful
|
// File sync successful
|
||||||
if (a._fileAction == "file_created") {
|
if (a._fileAction == "file_created") {
|
||||||
return "qrc:///client/theme/colored/add.svg";
|
return a._previews.empty() ? "qrc:///client/theme/colored/add.svg"
|
||||||
|
: "qrc:///client/theme/colored/add-bordered.svg";
|
||||||
} else if (a._fileAction == "file_deleted") {
|
} else if (a._fileAction == "file_deleted") {
|
||||||
return "qrc:///client/theme/colored/delete.svg";
|
return a._previews.empty() ? "qrc:///client/theme/colored/delete.svg"
|
||||||
|
: "qrc:///client/theme/colored/delete-bordered.svg";
|
||||||
} else {
|
} else {
|
||||||
return "qrc:///client/theme/change.svg";
|
return a._previews.empty() ? "qrc:///client/theme/change.svg"
|
||||||
|
: "qrc:///client/theme/colored/change-bordered.svg";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -286,6 +302,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
||||||
return !data(index, PathRole).toString().isEmpty() && a._objectType == QStringLiteral("files") && _displayActions && a._fileAction != "file_deleted" && a._status != SyncFileItem::FileIgnored;
|
return !data(index, PathRole).toString().isEmpty() && a._objectType == QStringLiteral("files") && _displayActions && a._fileAction != "file_deleted" && a._status != SyncFileItem::FileIgnored;
|
||||||
case IsCurrentUserFileActivityRole:
|
case IsCurrentUserFileActivityRole:
|
||||||
return a._isCurrentUserFileActivity;
|
return a._isCurrentUserFileActivity;
|
||||||
|
case ThumbnailRole: {
|
||||||
|
if(a._previews.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto preview = a._previews[0];
|
||||||
|
return(generatePreviewMap(preview));
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -324,6 +348,7 @@ void ActivityListModel::startFetchJob()
|
||||||
this, &ActivityListModel::activitiesReceived);
|
this, &ActivityListModel::activitiesReceived);
|
||||||
|
|
||||||
QUrlQuery params;
|
QUrlQuery params;
|
||||||
|
params.addQueryItem(QLatin1String("previews"), QLatin1String("true"));
|
||||||
params.addQueryItem(QLatin1String("since"), QString::number(_currentItem));
|
params.addQueryItem(QLatin1String("since"), QString::number(_currentItem));
|
||||||
params.addQueryItem(QLatin1String("limit"), QString::number(50));
|
params.addQueryItem(QLatin1String("limit"), QString::number(50));
|
||||||
job->addQueryParams(params);
|
job->addQueryParams(params);
|
||||||
|
@ -348,80 +373,17 @@ int ActivityListModel::currentItem() const
|
||||||
return _currentItem;
|
return _currentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode)
|
void ActivityListModel::ingestActivities(const QJsonArray &activities)
|
||||||
{
|
{
|
||||||
auto activities = json.object().value("ocs").toObject().value("data").toArray();
|
|
||||||
|
|
||||||
ActivityList list;
|
ActivityList list;
|
||||||
auto ast = _accountState;
|
|
||||||
if (!ast) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activities.size() == 0) {
|
|
||||||
_doneFetching = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentlyFetching = false;
|
|
||||||
|
|
||||||
QDateTime oldestDate = QDateTime::currentDateTime();
|
QDateTime oldestDate = QDateTime::currentDateTime();
|
||||||
oldestDate = oldestDate.addDays(_maxActivitiesDays * -1);
|
oldestDate = oldestDate.addDays(_maxActivitiesDays * -1);
|
||||||
|
|
||||||
foreach (auto activ, activities) {
|
for (const auto &activ : activities) {
|
||||||
auto json = activ.toObject();
|
const auto json = activ.toObject();
|
||||||
|
|
||||||
Activity a;
|
const auto a = Activity::fromActivityJson(json, _accountState->account());
|
||||||
const auto activityUser = json.value(QStringLiteral("user")).toString();
|
|
||||||
a._type = Activity::ActivityType;
|
|
||||||
a._objectType = json.value(QStringLiteral("object_type")).toString();
|
|
||||||
a._objectId = json.value(QStringLiteral("object_id")).toInt();
|
|
||||||
a._objectName = json.value(QStringLiteral("object_name")).toString();
|
|
||||||
a._accName = ast->account()->displayName();
|
|
||||||
a._id = json.value(QStringLiteral("activity_id")).toInt();
|
|
||||||
a._fileAction = json.value(QStringLiteral("type")).toString();
|
|
||||||
a._subject = json.value(QStringLiteral("subject")).toString();
|
|
||||||
a._message = json.value(QStringLiteral("message")).toString();
|
|
||||||
a._file = json.value(QStringLiteral("object_name")).toString();
|
|
||||||
a._link = QUrl(json.value(QStringLiteral("link")).toString());
|
|
||||||
a._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate);
|
|
||||||
a._icon = json.value(QStringLiteral("icon")).toString();
|
|
||||||
a._isCurrentUserFileActivity = a._objectType == QStringLiteral("files") && activityUser == ast->account()->davUser();
|
|
||||||
|
|
||||||
auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray();
|
|
||||||
|
|
||||||
if(richSubjectData.size() > 1) {
|
|
||||||
a._subjectRich = richSubjectData[0].toString();
|
|
||||||
auto parameters = richSubjectData[1].toObject();
|
|
||||||
const QRegularExpression subjectRichParameterRe(QStringLiteral("({[a-zA-Z0-9]*})"));
|
|
||||||
const QRegularExpression subjectRichParameterBracesRe(QStringLiteral("[{}]"));
|
|
||||||
|
|
||||||
for (auto i = parameters.begin(); i != parameters.end(); ++i) {
|
|
||||||
const auto parameterJsonObject = i.value().toObject();
|
|
||||||
const Activity::RichSubjectParameter parameter = {
|
|
||||||
parameterJsonObject.value(QStringLiteral("type")).toString(),
|
|
||||||
parameterJsonObject.value(QStringLiteral("id")).toString(),
|
|
||||||
parameterJsonObject.value(QStringLiteral("name")).toString(),
|
|
||||||
parameterJsonObject.contains(QStringLiteral("path")) ? parameterJsonObject.value(QStringLiteral("path")).toString() : QString(),
|
|
||||||
parameterJsonObject.contains(QStringLiteral("link")) ? QUrl(parameterJsonObject.value(QStringLiteral("link")).toString()) : QUrl(),
|
|
||||||
};
|
|
||||||
|
|
||||||
a._subjectRichParameters[i.key()] = parameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto displayString = a._subjectRich;
|
|
||||||
auto i = subjectRichParameterRe.globalMatch(displayString);
|
|
||||||
|
|
||||||
while (i.hasNext()) {
|
|
||||||
const auto match = i.next();
|
|
||||||
auto word = match.captured(1);
|
|
||||||
word.remove(subjectRichParameterBracesRe);
|
|
||||||
|
|
||||||
Q_ASSERT(a._subjectRichParameters.contains(word));
|
|
||||||
displayString = displayString.replace(match.captured(1), a._subjectRichParameters[word].name);
|
|
||||||
}
|
|
||||||
|
|
||||||
a._subjectDisplay = displayString;
|
|
||||||
}
|
|
||||||
|
|
||||||
list.append(a);
|
list.append(a);
|
||||||
_currentItem = list.last()._id;
|
_currentItem = list.last()._id;
|
||||||
|
@ -436,6 +398,23 @@ void ActivityListModel::activitiesReceived(const QJsonDocument &json, int status
|
||||||
}
|
}
|
||||||
|
|
||||||
_activityLists.append(list);
|
_activityLists.append(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityListModel::activitiesReceived(const QJsonDocument &json, int statusCode)
|
||||||
|
{
|
||||||
|
const auto activities = json.object().value(QStringLiteral("ocs")).toObject().value(QStringLiteral("data")).toArray();
|
||||||
|
|
||||||
|
if (!_accountState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activities.empty()) {
|
||||||
|
_doneFetching = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentlyFetching = false;
|
||||||
|
|
||||||
|
ingestActivities(activities);
|
||||||
|
|
||||||
combineActivityLists();
|
combineActivityLists();
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ public:
|
||||||
DisplayActions,
|
DisplayActions,
|
||||||
ShareableRole,
|
ShareableRole,
|
||||||
IsCurrentUserFileActivityRole,
|
IsCurrentUserFileActivityRole,
|
||||||
|
ThumbnailRole,
|
||||||
};
|
};
|
||||||
Q_ENUM(DataRole)
|
Q_ENUM(DataRole)
|
||||||
|
|
||||||
|
@ -136,6 +137,8 @@ private:
|
||||||
void combineActivityLists();
|
void combineActivityLists();
|
||||||
bool canFetchActivities() const;
|
bool canFetchActivities() const;
|
||||||
|
|
||||||
|
void ingestActivities(const QJsonArray &activities);
|
||||||
|
|
||||||
ActivityList _activityLists;
|
ActivityList _activityLists;
|
||||||
ActivityList _syncFileItemLists;
|
ActivityList _syncFileItemLists;
|
||||||
ActivityList _notificationLists;
|
ActivityList _notificationLists;
|
||||||
|
|
109
src/gui/tray/asyncimageresponse.cpp
Normal file
109
src/gui/tray/asyncimageresponse.cpp
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QSvgRenderer>
|
||||||
|
|
||||||
|
#include "asyncimageresponse.h"
|
||||||
|
#include "usermodel.h"
|
||||||
|
|
||||||
|
AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
if (id.isEmpty()) {
|
||||||
|
setImageAndEmitFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_imagePaths = id.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
||||||
|
_requestedImageSize = requestedSize;
|
||||||
|
|
||||||
|
if (_imagePaths.isEmpty()) {
|
||||||
|
setImageAndEmitFinished();
|
||||||
|
} else {
|
||||||
|
processNextImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncImageResponse::setImageAndEmitFinished(const QImage &image)
|
||||||
|
{
|
||||||
|
_image = image;
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickTextureFactory* AsyncImageResponse::textureFactory() const
|
||||||
|
{
|
||||||
|
return QQuickTextureFactory::textureFactoryForImage(_image);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncImageResponse::processNextImage()
|
||||||
|
{
|
||||||
|
if (_index < 0 || _index >= _imagePaths.size()) {
|
||||||
|
setImageAndEmitFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_imagePaths.at(_index).startsWith(QStringLiteral(":/client"))) {
|
||||||
|
setImageAndEmitFinished(QIcon(_imagePaths.at(_index)).pixmap(_requestedImageSize).toImage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto currentUser = OCC::UserModel::instance()->currentUser();
|
||||||
|
if (currentUser && currentUser->account()) {
|
||||||
|
const QUrl iconUrl(_imagePaths.at(_index));
|
||||||
|
if (iconUrl.isValid() && !iconUrl.scheme().isEmpty()) {
|
||||||
|
// fetch the remote resource
|
||||||
|
const auto reply = currentUser->account()->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &AsyncImageResponse::slotProcessNetworkReply);
|
||||||
|
++_index;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageAndEmitFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncImageResponse::slotProcessNetworkReply()
|
||||||
|
{
|
||||||
|
const auto reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
if (!reply) {
|
||||||
|
setImageAndEmitFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray imageData = reply->readAll();
|
||||||
|
// server returns "[]" for some some file previews (have no idea why), so, we use another image
|
||||||
|
// from the list if available
|
||||||
|
if (imageData.isEmpty() || imageData == QByteArrayLiteral("[]")) {
|
||||||
|
processNextImage();
|
||||||
|
} else {
|
||||||
|
if (imageData.startsWith(QByteArrayLiteral("<svg"))) {
|
||||||
|
// SVG image needs proper scaling, let's do it with QPainter and QSvgRenderer
|
||||||
|
QSvgRenderer svgRenderer;
|
||||||
|
if (svgRenderer.load(imageData)) {
|
||||||
|
QImage scaledSvg(_requestedImageSize, QImage::Format_ARGB32);
|
||||||
|
scaledSvg.fill("transparent");
|
||||||
|
QPainter painterForSvg(&scaledSvg);
|
||||||
|
svgRenderer.render(&painterForSvg);
|
||||||
|
setImageAndEmitFinished(scaledSvg);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
processNextImage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setImageAndEmitFinished(QImage::fromData(imageData));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
37
src/gui/tray/asyncimageresponse.h
Normal file
37
src/gui/tray/asyncimageresponse.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QQuickImageProvider>
|
||||||
|
|
||||||
|
class AsyncImageResponse : public QQuickImageResponse
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AsyncImageResponse(const QString &id, const QSize &requestedSize);
|
||||||
|
void setImageAndEmitFinished(const QImage &image = {});
|
||||||
|
QQuickTextureFactory *textureFactory() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void processNextImage();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void slotProcessNetworkReply();
|
||||||
|
|
||||||
|
QImage _image;
|
||||||
|
QStringList _imagePaths;
|
||||||
|
QSize _requestedImageSize;
|
||||||
|
int _index = 0;
|
||||||
|
};
|
25
src/gui/tray/trayimageprovider.cpp
Normal file
25
src/gui/tray/trayimageprovider.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "trayimageprovider.h"
|
||||||
|
#include "asyncimageresponse.h"
|
||||||
|
|
||||||
|
namespace OCC {
|
||||||
|
|
||||||
|
QQuickImageResponse *TrayImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
return new AsyncImageResponse(id, requestedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,12 +20,12 @@
|
||||||
namespace OCC {
|
namespace OCC {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The UnifiedSearchResultImageProvider
|
* @brief The TrayImageProvider
|
||||||
* @ingroup gui
|
* @ingroup gui
|
||||||
* Allows to fetch Unified Search result icon from the server or used a local resource
|
* Allows to fetch icon from the server or used a local resource
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class UnifiedSearchResultImageProvider : public QQuickAsyncImageProvider
|
class TrayImageProvider : public QQuickAsyncImageProvider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
|
@ -1,131 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) by Oleksandr Zolotov <alex@nextcloud.com>
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "unifiedsearchresultimageprovider.h"
|
|
||||||
|
|
||||||
#include "usermodel.h"
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QSvgRenderer>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class AsyncImageResponse : public QQuickImageResponse
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AsyncImageResponse(const QString &id, const QSize &requestedSize)
|
|
||||||
{
|
|
||||||
if (id.isEmpty()) {
|
|
||||||
setImageAndEmitFinished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_imagePaths = id.split(QLatin1Char(';'), Qt::SkipEmptyParts);
|
|
||||||
_requestedImageSize = requestedSize;
|
|
||||||
|
|
||||||
if (_imagePaths.isEmpty()) {
|
|
||||||
setImageAndEmitFinished();
|
|
||||||
} else {
|
|
||||||
processNextImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setImageAndEmitFinished(const QImage &image = {})
|
|
||||||
{
|
|
||||||
_image = image;
|
|
||||||
emit finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQuickTextureFactory *textureFactory() const override
|
|
||||||
{
|
|
||||||
return QQuickTextureFactory::textureFactoryForImage(_image);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void processNextImage()
|
|
||||||
{
|
|
||||||
if (_index < 0 || _index >= _imagePaths.size()) {
|
|
||||||
setImageAndEmitFinished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_imagePaths.at(_index).startsWith(QStringLiteral(":/client"))) {
|
|
||||||
setImageAndEmitFinished(QIcon(_imagePaths.at(_index)).pixmap(_requestedImageSize).toImage());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto currentUser = OCC::UserModel::instance()->currentUser();
|
|
||||||
if (currentUser && currentUser->account()) {
|
|
||||||
const QUrl iconUrl(_imagePaths.at(_index));
|
|
||||||
if (iconUrl.isValid() && !iconUrl.scheme().isEmpty()) {
|
|
||||||
// fetch the remote resource
|
|
||||||
const auto reply = currentUser->account()->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
|
|
||||||
connect(reply, &QNetworkReply::finished, this, &AsyncImageResponse::slotProcessNetworkReply);
|
|
||||||
++_index;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setImageAndEmitFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void slotProcessNetworkReply()
|
|
||||||
{
|
|
||||||
const auto reply = qobject_cast<QNetworkReply *>(sender());
|
|
||||||
if (!reply) {
|
|
||||||
setImageAndEmitFinished();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray imageData = reply->readAll();
|
|
||||||
// server returns "[]" for some some file previews (have no idea why), so, we use another image
|
|
||||||
// from the list if available
|
|
||||||
if (imageData.isEmpty() || imageData == QByteArrayLiteral("[]")) {
|
|
||||||
processNextImage();
|
|
||||||
} else {
|
|
||||||
if (imageData.startsWith(QByteArrayLiteral("<svg"))) {
|
|
||||||
// SVG image needs proper scaling, let's do it with QPainter and QSvgRenderer
|
|
||||||
QSvgRenderer svgRenderer;
|
|
||||||
if (svgRenderer.load(imageData)) {
|
|
||||||
QImage scaledSvg(_requestedImageSize, QImage::Format_ARGB32);
|
|
||||||
scaledSvg.fill("transparent");
|
|
||||||
QPainter painterForSvg(&scaledSvg);
|
|
||||||
svgRenderer.render(&painterForSvg);
|
|
||||||
setImageAndEmitFinished(scaledSvg);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
processNextImage();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setImageAndEmitFinished(QImage::fromData(imageData));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage _image;
|
|
||||||
QStringList _imagePaths;
|
|
||||||
QSize _requestedImageSize;
|
|
||||||
int _index = 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace OCC {
|
|
||||||
|
|
||||||
QQuickImageResponse *UnifiedSearchResultImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
|
||||||
{
|
|
||||||
return new AsyncImageResponse(id, requestedSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "tray/notificationcache.h"
|
#include "tray/notificationcache.h"
|
||||||
#include "tray/unifiedsearchresultslistmodel.h"
|
#include "tray/unifiedsearchresultslistmodel.h"
|
||||||
#include "userstatusconnector.h"
|
#include "userstatusconnector.h"
|
||||||
|
#include "thumbnailjob.h"
|
||||||
|
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
@ -499,50 +500,88 @@ bool User::isUnsolvableConflict(const SyncFileItemPtr &item) const
|
||||||
|
|
||||||
void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item)
|
void User::processCompletedSyncItem(const Folder *folder, const SyncFileItemPtr &item)
|
||||||
{
|
{
|
||||||
|
const auto fileActionFromInstruction = [](const int instruction) {
|
||||||
|
if (instruction == CSYNC_INSTRUCTION_REMOVE) {
|
||||||
|
return QStringLiteral("file_deleted");
|
||||||
|
} else if (instruction == CSYNC_INSTRUCTION_NEW) {
|
||||||
|
return QStringLiteral("file_created");
|
||||||
|
} else if (instruction == CSYNC_INSTRUCTION_RENAME) {
|
||||||
|
return QStringLiteral("file_renamed");
|
||||||
|
} else {
|
||||||
|
return QStringLiteral("file_changed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto messageFromFileAction = [](const QString &fileAction, const QString &fileName) {
|
||||||
|
if (fileAction == QStringLiteral("file_renamed")) {
|
||||||
|
return QObject::tr("You renamed %1").arg(fileName);
|
||||||
|
} else if (fileAction == QStringLiteral("file_deleted")) {
|
||||||
|
return QObject:: tr("You deleted %1").arg(fileName);
|
||||||
|
} else if (fileAction == QStringLiteral("file_created")) {
|
||||||
|
return QObject::tr("You created %1").arg(fileName);
|
||||||
|
} else {
|
||||||
|
return QObject::tr("You changed %1").arg(fileName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Activity activity;
|
Activity activity;
|
||||||
activity._type = Activity::SyncFileItemType; //client activity
|
activity._type = Activity::SyncFileItemType; //client activity
|
||||||
activity._status = item->_status;
|
activity._status = item->_status;
|
||||||
activity._dateTime = QDateTime::currentDateTime();
|
activity._dateTime = QDateTime::currentDateTime();
|
||||||
activity._message = item->_originalFile;
|
activity._message = item->_originalFile;
|
||||||
activity._link = folder->accountState()->account()->url();
|
activity._link = account()->url();
|
||||||
activity._accName = folder->accountState()->account()->displayName();
|
activity._accName = account()->displayName();
|
||||||
activity._file = item->_file;
|
activity._file = item->_file;
|
||||||
activity._folder = folder->alias();
|
activity._folder = folder->alias();
|
||||||
activity._fileAction = "";
|
activity._fileAction = "";
|
||||||
activity._objectId = item->_fileId.toInt();
|
|
||||||
activity._objectName = item->_file;
|
|
||||||
|
|
||||||
const auto fileName = QFileInfo(item->_originalFile).fileName();
|
const auto fileName = QFileInfo(item->_originalFile).fileName();
|
||||||
|
|
||||||
if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) {
|
activity._fileAction = fileActionFromInstruction(item->_instruction);
|
||||||
activity._fileAction = "file_deleted";
|
|
||||||
} else if (item->_instruction == CSYNC_INSTRUCTION_NEW) {
|
|
||||||
activity._fileAction = "file_created";
|
|
||||||
} else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) {
|
|
||||||
activity._fileAction = "file_renamed";
|
|
||||||
activity._renamedFile = item->_renameTarget;
|
|
||||||
} else {
|
|
||||||
activity._fileAction = "file_changed";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) {
|
if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) {
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
|
qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully.";
|
||||||
|
|
||||||
if (item->_direction != SyncFileItem::Up) {
|
if (item->_direction != SyncFileItem::Up) {
|
||||||
activity._message = tr("Synced %1").arg(fileName);
|
activity._message = QObject::tr("Synced %1").arg(fileName);
|
||||||
} else if (activity._fileAction == "file_renamed") {
|
|
||||||
activity._message = tr("You renamed %1").arg(fileName);
|
|
||||||
} else if (activity._fileAction == "file_deleted") {
|
|
||||||
activity._message = tr("You deleted %1").arg(fileName);
|
|
||||||
} else if (activity._fileAction == "file_created") {
|
|
||||||
activity._message = tr("You created %1").arg(fileName);
|
|
||||||
} else {
|
} else {
|
||||||
activity._message = tr("You changed %1").arg(fileName);
|
activity._message = messageFromFileAction(activity._fileAction, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activity._fileAction != "file_deleted") {
|
||||||
|
auto remotePath = folder->remotePath();
|
||||||
|
remotePath.append(activity._fileAction == "file_renamed" ? item->_renameTarget : activity._file);
|
||||||
|
|
||||||
|
const auto localFiles = FolderMan::instance()->findFileInLocalFolders(item->_file, account());
|
||||||
|
if (!localFiles.isEmpty()) {
|
||||||
|
const QMimeType mimeType = _mimeDb.mimeTypeForFile(QFileInfo(localFiles.constFirst()));
|
||||||
|
|
||||||
|
// Set the preview data, though for now we can skip setting file ID, link, and view
|
||||||
|
PreviewData preview;
|
||||||
|
preview._mimeType = mimeType.name();
|
||||||
|
preview._filename = fileName;
|
||||||
|
|
||||||
|
if(item->isDirectory()) {
|
||||||
|
preview._source = account()->url().toString() + QStringLiteral("/index.php/apps/theming/img/core/filetypes/folder.svg");
|
||||||
|
preview._isMimeTypeIcon = true;
|
||||||
|
} else if(mimeType.isValid() && mimeType.inherits("text/plain")) {
|
||||||
|
preview._source = account()->url().toString() + QStringLiteral("/index.php/apps/theming/img/core/filetypes/text.svg");
|
||||||
|
preview._isMimeTypeIcon = true;
|
||||||
|
} else if (mimeType.isValid() && mimeType.inherits("application/pdf")) {
|
||||||
|
preview._source = account()->url().toString() + QStringLiteral("/index.php/apps/theming/img/core/filetypes/application-pdf.svg");
|
||||||
|
preview._isMimeTypeIcon = true;
|
||||||
|
} else {
|
||||||
|
preview._source = account()->url().toString() + QStringLiteral("/index.php/apps/files/api/v1/thumbnail/150/150/") + remotePath;
|
||||||
|
preview._isMimeTypeIcon = false;
|
||||||
|
}
|
||||||
|
activity._previews.append(preview);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_activityModel->addSyncFileItemToActivityList(activity);
|
_activityModel->addSyncFileItemToActivityList(activity);
|
||||||
} else {
|
} else {
|
||||||
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
|
qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString;
|
||||||
|
|
||||||
activity._subject = item->_errorString;
|
activity._subject = item->_errorString;
|
||||||
|
|
||||||
if (item->_status == SyncFileItem::Status::FileIgnored) {
|
if (item->_status == SyncFileItem::Status::FileIgnored) {
|
||||||
|
|
|
@ -134,6 +134,7 @@ private:
|
||||||
|
|
||||||
QElapsedTimer _guiLogTimer;
|
QElapsedTimer _guiLogTimer;
|
||||||
NotificationCache _notificationCache;
|
NotificationCache _notificationCache;
|
||||||
|
QMimeDatabase _mimeDb;
|
||||||
|
|
||||||
// number of currently running notification requests. If non zero,
|
// number of currently running notification requests. If non zero,
|
||||||
// no query for notifications is started.
|
// no query for notifications is started.
|
||||||
|
|
|
@ -62,6 +62,7 @@ nextcloud_add_test(NotificationCache)
|
||||||
nextcloud_add_test(SetUserStatusDialog)
|
nextcloud_add_test(SetUserStatusDialog)
|
||||||
nextcloud_add_test(UnifiedSearchListmodel)
|
nextcloud_add_test(UnifiedSearchListmodel)
|
||||||
nextcloud_add_test(ActivityListModel)
|
nextcloud_add_test(ActivityListModel)
|
||||||
|
nextcloud_add_test(ActivityData)
|
||||||
|
|
||||||
if( UNIX AND NOT APPLE )
|
if( UNIX AND NOT APPLE )
|
||||||
nextcloud_add_test(InotifyWatcher)
|
nextcloud_add_test(InotifyWatcher)
|
||||||
|
|
184
test/testactivitydata.cpp
Normal file
184
test/testactivitydata.cpp
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) by Claudio Cambra <claudio.cambra@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 "gui/tray/activitydata.h"
|
||||||
|
#include "account.h"
|
||||||
|
#include "accountstate.h"
|
||||||
|
#include "configfile.h"
|
||||||
|
#include "syncenginetestutils.h"
|
||||||
|
#include "syncfileitem.h"
|
||||||
|
#include "folder.h"
|
||||||
|
#include "folderman.h"
|
||||||
|
#include "testhelper.h"
|
||||||
|
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
class TestActivityData : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestActivityData() = default;
|
||||||
|
|
||||||
|
void createJsonSpecificFormatData(QString fileFormat, QString mimeType)
|
||||||
|
{
|
||||||
|
const auto objectType = QStringLiteral("files");
|
||||||
|
const auto subject = QStringLiteral("You created path/test.").append(fileFormat);
|
||||||
|
const auto path = QStringLiteral("path/test.").append(fileFormat);
|
||||||
|
const auto fileName = QStringLiteral("test.").append(fileFormat);
|
||||||
|
const auto activityType = QStringLiteral("file");
|
||||||
|
const auto activityId = 90000;
|
||||||
|
const auto message = QStringLiteral();
|
||||||
|
const auto objectName = QStringLiteral("test.").append(fileFormat);
|
||||||
|
const auto link = account->url().toString().append(QStringLiteral("/f/")).append(activityId);
|
||||||
|
const auto datetime = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||||
|
const auto icon = account->url().toString().append(QStringLiteral("/apps/files/img/add-color.svg"));
|
||||||
|
|
||||||
|
const QJsonObject richStringData({
|
||||||
|
{QStringLiteral("type"), activityType},
|
||||||
|
{QStringLiteral("id"), activityId},
|
||||||
|
{QStringLiteral("link"), link},
|
||||||
|
{QStringLiteral("name"), fileName},
|
||||||
|
{QStringLiteral("path"), objectName}
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto subjectRichString = QStringLiteral("You created {file1}");
|
||||||
|
const auto subjectRichObj = QJsonObject({{QStringLiteral("file1"), richStringData}});
|
||||||
|
const auto subjectRichData = QJsonArray({subjectRichString, subjectRichObj});
|
||||||
|
|
||||||
|
const auto previewUrl = account->url().toString().append(QStringLiteral("/index.php/core/preview.png?file=/")).append(path);
|
||||||
|
|
||||||
|
// Text file previews should be replaced by mimetype icon
|
||||||
|
const QJsonObject previewData({
|
||||||
|
{QStringLiteral("link"), link},
|
||||||
|
{QStringLiteral("mimeType"), mimeType},
|
||||||
|
{QStringLiteral("fileId"), activityId},
|
||||||
|
{QStringLiteral("filename"), fileName},
|
||||||
|
{QStringLiteral("view"), QStringLiteral("files")},
|
||||||
|
{QStringLiteral("source"), previewUrl},
|
||||||
|
{QStringLiteral("isMimeTypeIcon"), false},
|
||||||
|
});
|
||||||
|
|
||||||
|
QJsonObject testData({
|
||||||
|
{QStringLiteral("object_type"), objectType},
|
||||||
|
{QStringLiteral("activity_id"), activityId},
|
||||||
|
{QStringLiteral("type"), activityType},
|
||||||
|
{QStringLiteral("subject"), subject},
|
||||||
|
{QStringLiteral("message"), message},
|
||||||
|
{QStringLiteral("object_name"), objectName},
|
||||||
|
{QStringLiteral("link"), link},
|
||||||
|
{QStringLiteral("datetime"), datetime},
|
||||||
|
{QStringLiteral("icon"), icon},
|
||||||
|
{QStringLiteral("subject_rich"), subjectRichData},
|
||||||
|
{QStringLiteral("previews"), QJsonArray({previewData})},
|
||||||
|
});
|
||||||
|
|
||||||
|
QTest::addRow("data") << testData << fileFormat << mimeType << objectType << subject << path << fileName << activityType << activityId << message << objectName << link << datetime << icon << subjectRichString << subjectRichData << previewUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScopedPointer<FakeQNAM> fakeQnam;
|
||||||
|
OCC::AccountPtr account;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void initTestCase()
|
||||||
|
{
|
||||||
|
account = OCC::Account::create();
|
||||||
|
account->setCredentials(new FakeCredentials{fakeQnam.data()});
|
||||||
|
account->setUrl(QUrl(("http://example.de")));
|
||||||
|
auto *cred = new HttpCredentialsTest("testuser", "secret");
|
||||||
|
account->setCredentials(cred);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testFromJson_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QJsonObject>("activityJsonObject");
|
||||||
|
QTest::addColumn<QString>("fileFormat");
|
||||||
|
QTest::addColumn<QString>("mimeTypeExpected");
|
||||||
|
QTest::addColumn<QString>("objectTypeExpected");
|
||||||
|
QTest::addColumn<QString>("subjectExpected");
|
||||||
|
QTest::addColumn<QString>("pathExpected");
|
||||||
|
QTest::addColumn<QString>("fileNameExpected");
|
||||||
|
QTest::addColumn<QString>("activityTypeExpected");
|
||||||
|
QTest::addColumn<int>("activityIdExpected");
|
||||||
|
QTest::addColumn<QString>("messageExpected");
|
||||||
|
QTest::addColumn<QString>("objectNameExpected");
|
||||||
|
QTest::addColumn<QString>("linkExpected");
|
||||||
|
QTest::addColumn<QString>("datetimeExpected");
|
||||||
|
QTest::addColumn<QString>("iconExpected");
|
||||||
|
QTest::addColumn<QString>("subjectRichStringExpected");
|
||||||
|
QTest::addColumn<QJsonArray>("subjectRichDataExpected");
|
||||||
|
QTest::addColumn<QString>("previewUrlExpected");
|
||||||
|
|
||||||
|
createJsonSpecificFormatData(QStringLiteral("jpg"), QStringLiteral("image/jpg"));
|
||||||
|
createJsonSpecificFormatData(QStringLiteral("txt"), QStringLiteral("text/plain"));
|
||||||
|
createJsonSpecificFormatData(QStringLiteral("pdf"), QStringLiteral("application/pdf"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void testFromJson()
|
||||||
|
{
|
||||||
|
QFETCH(QJsonObject, activityJsonObject);
|
||||||
|
QFETCH(QString, fileFormat);
|
||||||
|
QFETCH(QString, mimeTypeExpected);
|
||||||
|
QFETCH(QString, objectTypeExpected);
|
||||||
|
QFETCH(QString, subjectExpected);
|
||||||
|
QFETCH(QString, pathExpected);
|
||||||
|
QFETCH(QString, fileNameExpected);
|
||||||
|
QFETCH(QString, activityTypeExpected);
|
||||||
|
QFETCH(int, activityIdExpected);
|
||||||
|
QFETCH(QString, messageExpected);
|
||||||
|
QFETCH(QString, objectNameExpected);
|
||||||
|
QFETCH(QString, linkExpected);
|
||||||
|
QFETCH(QString, datetimeExpected);
|
||||||
|
QFETCH(QString, iconExpected);
|
||||||
|
QFETCH(QString, subjectRichStringExpected);
|
||||||
|
QFETCH(QJsonArray, subjectRichDataExpected);
|
||||||
|
QFETCH(QString, previewUrlExpected);
|
||||||
|
|
||||||
|
OCC::Activity activity = OCC::Activity::fromActivityJson(activityJsonObject, account);
|
||||||
|
QCOMPARE(activity._type, OCC::Activity::ActivityType);
|
||||||
|
QCOMPARE(activity._objectType, objectTypeExpected);
|
||||||
|
QCOMPARE(activity._id, activityIdExpected);
|
||||||
|
QCOMPARE(activity._fileAction, activityTypeExpected);
|
||||||
|
QCOMPARE(activity._accName, account->displayName());
|
||||||
|
QCOMPARE(activity._subject, subjectExpected);
|
||||||
|
QCOMPARE(activity._message, messageExpected);
|
||||||
|
QCOMPARE(activity._file, objectNameExpected);
|
||||||
|
QCOMPARE(activity._link, linkExpected);
|
||||||
|
QCOMPARE(activity._dateTime, QDateTime::fromString(datetimeExpected, Qt::ISODate));
|
||||||
|
|
||||||
|
QCOMPARE(activity._subjectRichParameters.count(), 1);
|
||||||
|
QCOMPARE(activity._subjectDisplay, QStringLiteral("You created ").append(fileNameExpected));
|
||||||
|
|
||||||
|
QCOMPARE(activity._previews.count(), 1);
|
||||||
|
// We want the different icon when we have a preview
|
||||||
|
//QCOMPARE(activity._icon, iconExpected);
|
||||||
|
|
||||||
|
if(fileFormat == "txt") {
|
||||||
|
QCOMPARE(activity._previews[0]._source, account->url().toString().append(QStringLiteral("/index.php/apps/theming/img/core/filetypes/text.svg")));
|
||||||
|
QCOMPARE(activity._previews[0]._isMimeTypeIcon, true);
|
||||||
|
QCOMPARE(activity._previews[0]._mimeType, mimeTypeExpected);
|
||||||
|
} else if(fileFormat == "pdf") {
|
||||||
|
QCOMPARE(activity._previews[0]._source, account->url().toString().append(QStringLiteral("/index.php/apps/theming/img/core/filetypes/application-pdf.svg")));
|
||||||
|
QCOMPARE(activity._previews[0]._isMimeTypeIcon, true);
|
||||||
|
} else {
|
||||||
|
QCOMPARE(activity._previews[0]._source, previewUrlExpected);
|
||||||
|
QCOMPARE(activity._previews[0]._isMimeTypeIcon, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(activity._previews[0]._mimeType, mimeTypeExpected);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_MAIN(TestActivityData)
|
||||||
|
#include "testactivitydata.moc"
|
|
@ -190,13 +190,16 @@
|
||||||
<file>theme/copy.svg</file>
|
<file>theme/copy.svg</file>
|
||||||
<file>theme/more.svg</file>
|
<file>theme/more.svg</file>
|
||||||
<file>theme/change.svg</file>
|
<file>theme/change.svg</file>
|
||||||
|
<file>theme/colored/change-bordered.svg</file>
|
||||||
<file>theme/lock-http.svg</file>
|
<file>theme/lock-http.svg</file>
|
||||||
<file>theme/lock-https.svg</file>
|
<file>theme/lock-https.svg</file>
|
||||||
<file>theme/lock-broken.svg</file>
|
<file>theme/lock-broken.svg</file>
|
||||||
<file>theme/network.svg</file>
|
<file>theme/network.svg</file>
|
||||||
<file>theme/account.svg</file>
|
<file>theme/account.svg</file>
|
||||||
<file>theme/colored/add.svg</file>
|
<file>theme/colored/add.svg</file>
|
||||||
|
<file>theme/colored/add-bordered.svg</file>
|
||||||
<file>theme/colored/delete.svg</file>
|
<file>theme/colored/delete.svg</file>
|
||||||
|
<file>theme/colored/delete-bordered.svg</file>
|
||||||
<file>theme/colored/@APPLICATION_ICON_NAME@-icon.svg</file>
|
<file>theme/colored/@APPLICATION_ICON_NAME@-icon.svg</file>
|
||||||
<file>theme/add.svg</file>
|
<file>theme/add.svg</file>
|
||||||
<file>theme/share.svg</file>
|
<file>theme/share.svg</file>
|
||||||
|
|
|
@ -29,6 +29,8 @@ QtObject {
|
||||||
property int trayWindowRadius: 10
|
property int trayWindowRadius: 10
|
||||||
property int trayWindowBorderWidth: 1
|
property int trayWindowBorderWidth: 1
|
||||||
property int trayWindowHeaderHeight: variableSize(60)
|
property int trayWindowHeaderHeight: variableSize(60)
|
||||||
|
property int trayHorizontalMargin: 10
|
||||||
|
property int trayListItemIconSize: accountAvatarSize
|
||||||
|
|
||||||
property int currentAccountButtonWidth: 220
|
property int currentAccountButtonWidth: 220
|
||||||
property int currentAccountButtonRadius: 2
|
property int currentAccountButtonRadius: 2
|
||||||
|
@ -58,7 +60,7 @@ QtObject {
|
||||||
property int roundedButtonBackgroundVerticalMargins: 5
|
property int roundedButtonBackgroundVerticalMargins: 5
|
||||||
|
|
||||||
property int userStatusEmojiSize: 8
|
property int userStatusEmojiSize: 8
|
||||||
property int userStatusSpacing: 6
|
property int userStatusSpacing: trayHorizontalMargin
|
||||||
property int userStatusAnchorsMargin: 2
|
property int userStatusAnchorsMargin: 2
|
||||||
property int accountServerAnchorsMargin: 10
|
property int accountServerAnchorsMargin: 10
|
||||||
property int accountLabelsSpacing: 4
|
property int accountLabelsSpacing: 4
|
||||||
|
|
43
theme/colored/add-bordered.svg
Normal file
43
theme/colored/add-bordered.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
version="1.1"
|
||||||
|
viewbox="0 0 16 16"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="add-bordered.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview6"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="39.375"
|
||||||
|
inkscape:cx="7.9873016"
|
||||||
|
inkscape:cy="8"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1020"
|
||||||
|
inkscape:window-x="1"
|
||||||
|
inkscape:window-y="1111"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<path
|
||||||
|
fill="#000000"
|
||||||
|
d="m 9.02,13.98 h -2 V 8.9799996 h -5 V 6.98 h 5 v -5 h 2 v 5 l 5,-0.028 v 2.0279996 h -5 z"
|
||||||
|
id="path2929"
|
||||||
|
style="fill:#000001;fill-opacity:1;stroke:#ffffff;stroke-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||||
|
<path
|
||||||
|
fill="#000"
|
||||||
|
d="M9.02 13.98h-2v-5h-5v-2h5v-5h2v5l5-.028V8.98h-5z"
|
||||||
|
id="path2" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
49
theme/colored/change-bordered.svg
Normal file
49
theme/colored/change-bordered.svg
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
width="16"
|
||||||
|
version="1.1"
|
||||||
|
height="16"
|
||||||
|
id="svg6"
|
||||||
|
sodipodi:docname="change-bordered.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs10" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview8"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="27.84233"
|
||||||
|
inkscape:cx="6.0878526"
|
||||||
|
inkscape:cy="3.9867353"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1020"
|
||||||
|
inkscape:window-x="1"
|
||||||
|
inkscape:window-y="1111"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg6" />
|
||||||
|
<path
|
||||||
|
d="m2 8.75v4.5l1.408-1.41c1.116 1.334 2.817 2.145 4.592 2.16 2.16 0.01827 4.116-1.132 5.196-3.002l-1.948-1.125c-0.677 1.171-1.9005 1.886-3.248 1.875-1.18-0.01-2.3047-0.572-3-1.5l1.5-1.5z"
|
||||||
|
id="path2731"
|
||||||
|
style="stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke:#ffffff;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
d="M 8,2 C 5.858,2 3.875,3.145 2.804,5 L 4.752,6.125 C 5.423,4.963 6.658,4.25 7.9996,4.25 c 1.1906,0 2.297,0.56157 3,1.5 l -1.5,1.5 h 4.5 v -4.5 l -1.406,1.406 C 11.4646,2.808 9.792,2 8,2 Z"
|
||||||
|
id="path827"
|
||||||
|
style="stroke:#ffffff;stroke-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none"
|
||||||
|
sodipodi:nodetypes="cccsccccccc" />
|
||||||
|
<path
|
||||||
|
d="m8 2c-2.142 0-4.125 1.145-5.196 3l1.948 1.125c0.671-1.162 1.906-1.875 3.2476-1.875 1.1906 0 2.297 0.56157 3 1.5l-1.5 1.5h4.5v-4.5l-1.406 1.406c-1.129-1.348-2.802-2.1563-4.594-2.1563z"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
d="m2 8.75v4.5l1.408-1.41c1.116 1.334 2.817 2.145 4.592 2.16 2.16 0.01827 4.116-1.132 5.196-3.002l-1.948-1.125c-0.677 1.171-1.9005 1.886-3.248 1.875-1.18-0.01-2.3047-0.572-3-1.5l1.5-1.5z"
|
||||||
|
id="path4" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
43
theme/colored/delete-bordered.svg
Normal file
43
theme/colored/delete-bordered.svg
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
height="16"
|
||||||
|
width="16"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="delete-bordered.svg"
|
||||||
|
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview6"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="39.375"
|
||||||
|
inkscape:cx="7.9873016"
|
||||||
|
inkscape:cy="8"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1020"
|
||||||
|
inkscape:window-x="1"
|
||||||
|
inkscape:window-y="1111"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<path
|
||||||
|
d="m3.0503 4.4645 3.5355 3.5355-3.5355 3.536 1.4142 1.414 3.5355-3.5358 3.536 3.5358 1.414-1.414-3.5358-3.536 3.5358-3.5355-1.414-1.4142-3.536 3.5355-3.5355-3.5355-1.4142 1.4142z"
|
||||||
|
fill="#000"
|
||||||
|
id="path849"
|
||||||
|
style="stroke:#ffffff;stroke-opacity:1;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" />
|
||||||
|
<path
|
||||||
|
d="m3.0503 4.4645 3.5355 3.5355-3.5355 3.536 1.4142 1.414 3.5355-3.5358 3.536 3.5358 1.414-1.414-3.5358-3.536 3.5358-3.5355-1.414-1.4142-3.536 3.5355-3.5355-3.5355-1.4142 1.4142z"
|
||||||
|
fill="#000"
|
||||||
|
id="path2" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
Loading…
Reference in a new issue