Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Talk Reply v1 #4200

Merged
merged 1 commit into from
Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
<file>src/gui/tray/ActivityItemContextMenu.qml</file>
<file>src/gui/tray/ActivityItemActions.qml</file>
<file>src/gui/tray/ActivityItemContent.qml</file>
<file>src/gui/tray/TalkReplyTextField.qml</file>
</qresource>
</RCC>
2 changes: 1 addition & 1 deletion src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ set(client_SRCS
tray/notificationcache.h
tray/notificationcache.cpp
creds/credentialsfactory.h
tray/talkreply.cpp
creds/credentialsfactory.cpp
creds/httpcredentialsgui.h
creds/httpcredentialsgui.cpp
Expand Down Expand Up @@ -392,7 +393,6 @@ function(generate_sized_png_from_svg icon_path size)
if (ARG_OUTPUT_ICON_PATH)
set(icon_name_dir ${ARG_OUTPUT_ICON_PATH})
endif ()


camilasan marked this conversation as resolved.
Show resolved Hide resolved
if (EXISTS "${icon_name_dir}/${size}-${icon_name_wle}.png")
return()
Expand Down
1 change: 1 addition & 0 deletions src/gui/systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class Systray
void showWindow();
void openShareDialog(const QString &sharePath, const QString &localPath);
void showFileActivityDialog(const QString &objectName, const int objectId);
void sendChatMessage(const QString &token, const QString &message, const QString &replyTo);

public slots:
void slotNewUserSelected();
Expand Down
4 changes: 3 additions & 1 deletion src/gui/tray/ActivityItem.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ MouseArea {

property bool isFileActivityList: false

property bool isChatActivity: model.objectType === "chat" || model.objectType === "room"
property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
property bool isTalkReplyPossible: model.conversationToken !== ""

signal fileActivityButtonClicked(string absolutePath)

Expand Down Expand Up @@ -67,6 +68,7 @@ MouseArea {
Layout.fillWidth: true
Layout.leftMargin: 40
Layout.bottomMargin: model.links.length > 1 ? 5 : 0
Layout.topMargin: isTalkReplyPossible? 48 : 0

displayActions: model.displayActions
objectType: model.objectType
Expand Down
17 changes: 15 additions & 2 deletions src/gui/tray/ActivityItemContent.qml
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,22 @@ RowLayout {
maximumLineCount: 2
font.pixelSize: Style.subLinePixelSize
color: "#808080"
}
}
}

Loader {
id: talkReplyTextFieldLoader
active: isChatActivity && isTalkReplyPossible

anchors.top: activityTextDateTime.bottom
anchors.topMargin: 10

sourceComponent: TalkReplyTextField {
id: talkReplyMessage
anchors.fill: parent
}
}
}

Button {
id: dismissActionButton

Expand Down
76 changes: 76 additions & 0 deletions src/gui/tray/TalkReplyTextField.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import QtQuick 2.15
import Style 1.0
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import com.nextcloud.desktopclient 1.0

Item {
id: root

function sendReplyMessage() {
if (replyMessageTextField.text === "") {
return;
}

UserModel.currentUser.sendReplyMessage(model.conversationToken, replyMessageTextField.text, model.messageId);
replyMessageSent.text = replyMessageTextField.text;
replyMessageTextField.clear();
}

Text {
id: replyMessageSent
font.pixelSize: Style.topLinePixelSize
color: Style.menuBorder
visible: replyMessageSent.text !== ""
}

TextField {
id: replyMessageTextField

// TODO use Layout to manage width/height. The Layout.minimunWidth does not apply to the width set.
height: 38
width: 250

onAccepted: root.sendReplyMessage()
visible: replyMessageSent.text === ""

topPadding: 4

placeholderText: qsTr("Reply to …")

background: Rectangle {
id: replyMessageTextFieldBorder
radius: 24
border.width: 1
border.color: Style.ncBlue
}

Button {
id: sendReplyMessageButton
width: 32
height: parent.height
opacity: 0.8
flat: true
enabled: replyMessageTextField.text !== ""
onClicked: root.sendReplyMessage()

icon {
source: "image://svgimage-custom-color/send.svg" + "/" + Style.ncBlue
width: 38
height: 38
color: hovered || !sendReplyMessageButton.enabled? Style.menuBorder : Style.ncBlue
}

anchors {
right: replyMessageTextField.right
top: replyMessageTextField.top
}

ToolTip {
visible: sendReplyMessageButton.hovered
delay: Qt.styleHints.mousePressAndHoldInterval
text: qsTr("Send reply to chat message")
}
}
}
}
6 changes: 6 additions & 0 deletions src/gui/tray/activitydata.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,16 @@ class Activity
QUrl link; // Optional (files only)
};

struct TalkNotificationData {
QString conversationToken;
QString messageId;
};

Type _type;
qlonglong _id;
QString _fileAction;
int _objectId;
TalkNotificationData _talkNotificationData;
QString _objectType;
QString _objectName;
QString _subject;
Expand Down
7 changes: 7 additions & 0 deletions src/gui/tray/activitylistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[ShareableRole] = "isShareable";
roles[IsCurrentUserFileActivityRole] = "isCurrentUserFileActivity";
roles[ThumbnailRole] = "thumbnail";
roles[TalkConversationTokenRole] = "conversationToken";
roles[TalkMessageIdRole] = "messageId";

return roles;
}

Expand Down Expand Up @@ -310,6 +313,10 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
const auto preview = a._previews[0];
return(generatePreviewMap(preview));
}
case TalkConversationTokenRole:
return a._talkNotificationData.conversationToken;
case TalkMessageIdRole:
return a._talkNotificationData.messageId;
default:
return QVariant();
}
Expand Down
2 changes: 2 additions & 0 deletions src/gui/tray/activitylistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class ActivityListModel : public QAbstractListModel
ShareableRole,
IsCurrentUserFileActivityRole,
ThumbnailRole,
TalkConversationTokenRole,
TalkMessageIdRole,
};
Q_ENUM(DataRole)

Expand Down
15 changes: 15 additions & 0 deletions src/gui/tray/notificationhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j

//need to know, specially for remote_share
a._objectType = json.value("object_type").toString();

// 2 cases to consider:
// - server == 24 & has Talk: notification type chat/call contains conversationToken/messageId in object_type
// - server < 24 & has Talk: notification type chat/call contains _only_ the conversationToken in object_type
if (a._objectType == "chat" || a._objectType == "call") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@camilasan just curious if "room" should also be handled similarly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question, I will have to check it. I noticed in your recent changes that you were checking for 'room'.

const auto objectId = json.value("object_id").toString();
const auto objectIdData = objectId.split("/");
a._talkNotificationData.conversationToken = objectIdData.first();
if (a._objectType == "chat" && objectIdData.size() > 1) {
a._talkNotificationData.messageId = objectIdData.last();
} else {
qCInfo(lcServerNotification) << "Replying directly to Talk conversation" << a._talkNotificationData.conversationToken << "will not be possible because the notification doesn't contain the message ID.";
}
}

a._status = 0;

a._subject = json.value("subject").toString();
Expand Down
44 changes: 44 additions & 0 deletions src/gui/tray/talkreply.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include "talkreply.h"
#include "accountstate.h"

#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>

namespace OCC {

Q_LOGGING_CATEGORY(lcTalkReply, "nextcloud.gui.talkreply", QtInfoMsg)

TalkReply::TalkReply(AccountState *accountState, QObject *parent)
: QObject(parent)
, _accountState(accountState)
{
Q_ASSERT(_accountState && _accountState->account());
}

void TalkReply::sendReplyMessage(const QString &conversationToken, const QString &message, const QString &replyTo)
{
QPointer<JsonApiJob> apiJob = new JsonApiJob(_accountState->account(),
camilasan marked this conversation as resolved.
Show resolved Hide resolved
QLatin1String("ocs/v2.php/apps/spreed/api/v1/chat/%1").arg(conversationToken),
this);

QObject::connect(apiJob, &JsonApiJob::jsonReceived, this, [&](const QJsonDocument &response, const int statusCode) {
if(statusCode != 200) {
qCWarning(lcTalkReply) << "Status code" << statusCode;
}

const auto responseObj = response.object().value("ocs").toObject().value("data").toObject();
emit replyMessageSent(responseObj.value("message").toString());
camilasan marked this conversation as resolved.
Show resolved Hide resolved

deleteLater();
});

QUrlQuery params;
params.addQueryItem(QStringLiteral("message"), message);
params.addQueryItem(QStringLiteral("replyTo"), QString(replyTo));

apiJob->addQueryParams(params);
apiJob->setVerb(JsonApiJob::Verb::Post);
apiJob->start();
}
}
24 changes: 24 additions & 0 deletions src/gui/tray/talkreply.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include <QtCore>
#include <QPointer>

namespace OCC {
class AccountState;

class TalkReply : public QObject
{
Q_OBJECT

public:
explicit TalkReply(AccountState *accountState, QObject *parent = nullptr);

void sendReplyMessage(const QString &conversationToken, const QString &message, const QString &replyTo = {});

signals:
void replyMessageSent(const QString &message);

private:
AccountState *_accountState = nullptr;
};
}
9 changes: 9 additions & 0 deletions src/gui/tray/usermodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "tray/activitylistmodel.h"
#include "tray/notificationcache.h"
#include "tray/unifiedsearchresultslistmodel.h"
#include "tray/talkreply.h"
#include "userstatusconnector.h"
#include "thumbnailjob.h"

Expand Down Expand Up @@ -79,6 +80,8 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
connect(_account->account().data(), &Account::capabilitiesChanged, this, &User::accentColorChanged);

connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);

camilasan marked this conversation as resolved.
Show resolved Hide resolved
connect(this, &User::sendReplyMessage, this, &User::slotSendReplyMessage);
}

void User::showDesktopNotification(const QString &title, const QString &message)
Expand Down Expand Up @@ -785,6 +788,12 @@ void User::removeAccount() const
AccountManager::instance()->save();
}

void User::slotSendReplyMessage(const QString &token, const QString &message, const QString &replyTo)
{
QPointer<TalkReply> talkReply = new TalkReply(_account.data(), this);
camilasan marked this conversation as resolved.
Show resolved Hide resolved
talkReply->sendReplyMessage(token, message, replyTo);
}

/*-------------------------------------------------------------------------------------*/

UserModel *UserModel::_instance = nullptr;
Expand Down
4 changes: 4 additions & 0 deletions src/gui/tray/usermodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class User : public QObject
Q_PROPERTY(QString avatar READ avatarUrl NOTIFY avatarChanged)
Q_PROPERTY(bool isConnected READ isConnected NOTIFY accountStateChanged)
Q_PROPERTY(UnifiedSearchResultsListModel* unifiedSearchResultsListModel READ getUnifiedSearchResultsListModel CONSTANT)

public:
User(AccountStatePtr &account, const bool &isCurrent = false, QObject *parent = nullptr);

Expand Down Expand Up @@ -86,6 +87,7 @@ class User : public QObject
void headerColorChanged();
void headerTextColorChanged();
void accentColorChanged();
void sendReplyMessage(const QString &token, const QString &message, const QString &replyTo);

public slots:
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
Expand All @@ -105,6 +107,7 @@ public slots:
void slotRefreshImmediately();
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
void slotRebuildNavigationAppList();
void slotSendReplyMessage(const QString &conversationToken, const QString &message, const QString &replyTo);

private:
void slotPushNotificationsReady();
Expand Down Expand Up @@ -139,6 +142,7 @@ public slots:
// number of currently running notification requests. If non zero,
// no query for notifications is started.
int _notificationRequestsRunning;
QString textSentStr;
};

class UserModel : public QAbstractListModel
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ nextcloud_add_test(SetUserStatusDialog)
nextcloud_add_test(UnifiedSearchListmodel)
nextcloud_add_test(ActivityListModel)
nextcloud_add_test(ActivityData)
nextcloud_add_test(TalkReply)

if( UNIX AND NOT APPLE )
nextcloud_add_test(InotifyWatcher)
Expand Down
Loading