Merge pull request #4393 from nextcloud/bugfix/talk-reply-part-2

Bugfix/talk reply part 2
This commit is contained in:
Camila 2022-04-06 11:16:47 +02:00 committed by GitHub
commit bd8a283fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 84 additions and 72 deletions

View File

@ -12,8 +12,9 @@ MouseArea {
property bool isFileActivityList: false
property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
property bool isTalkReplyPossible: model.conversationToken !== ""
readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
readonly property bool isTalkReplyPossible: model.conversationToken !== ""
property bool isTalkReplyOptionVisible: model.messageSent !== ""
signal fileActivityButtonClicked(string absolutePath)
@ -26,6 +27,10 @@ MouseArea {
Accessible.name: (model.path !== "" && model.displayPath !== "") ? qsTr("Open %1 locally").arg(model.displayPath) : model.message
Accessible.onPressAction: root.clicked()
function showReplyOptions() {
isTalkReplyOptionVisible = !isTalkReplyOptionVisible
}
Rectangle {
id: activityHover
anchors.fill: parent
@ -73,11 +78,11 @@ MouseArea {
ActivityItemActions {
id: activityActions
visible: !root.isFileActivityList && model.linksForActionButtons.length > 0 && !model.displayReplyOption
visible: !root.isFileActivityList && model.linksForActionButtons.length > 0 && !isTalkReplyOptionVisible
Layout.preferredHeight: Style.trayWindowHeaderHeight * 0.85
Layout.fillWidth: true
Layout.leftMargin: 40
Layout.leftMargin: 60
Layout.bottomMargin: model.links.length > 1 ? 5 : 0
displayActions: model.displayActions

View File

@ -31,7 +31,9 @@ RowLayout {
ActivityActionButton {
id: activityActionButton
readonly property bool primary: model.index === 0 && model.modelData.verb !== "DELETE"
readonly property string verb: model.modelData.verb
readonly property bool primary: model.index === 0 && verb !== "DELETE"
readonly property bool isTalkReplyButton: verb === "REPLY"
Layout.minimumWidth: primary ? Style.activityItemActionPrimaryButtonMinWidth : Style.activityItemActionSecondaryButtonMinWidth
Layout.preferredHeight: primary ? parent.height : parent.height * 0.3
@ -48,7 +50,7 @@ RowLayout {
bold: primary
onClicked: root.triggerAction(model.index)
onClicked: !isTalkReplyButton? root.triggerAction(model.index) : showReplyOptions(model.index)
}
}

View File

@ -131,10 +131,22 @@ RowLayout {
color: Style.ncSecondaryTextColor
}
Label {
id: talkReplyMessageSent
text: root.activityData.messageSent
height: implicitHeight
width: parent.width
elide: Text.ElideRight
wrapMode: Text.Wrap
maximumLineCount: 2
font.pixelSize: Style.topLinePixelSize
color: Style.ncSecondaryTextColor
}
Loader {
id: talkReplyTextFieldLoader
active: isChatActivity && isTalkReplyPossible && model.displayReplyOption
visible: model.displayReplyOption
active: isChatActivity && isTalkReplyPossible
visible: isTalkReplyOptionVisible
anchors.top: activityTextDateTime.bottom
anchors.topMargin: 10

View File

@ -13,25 +13,18 @@ Item {
}
UserModel.currentUser.sendReplyMessage(model.index, model.conversationToken, replyMessageTextField.text, model.messageId);
}
Text {
id: replyMessageSent
text: model.messageSent
font.pixelSize: Style.topLinePixelSize
color: Style.menuBorder
visible: model.messageSent !== ""
replyMessageTextField.visible = false
}
TextField {
id: replyMessageTextField
visible: model.messageSent === ""
// 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
@ -41,7 +34,7 @@ Item {
id: replyMessageTextFieldBorder
radius: 24
border.width: 1
border.color: Style.ncBlue
border.color: parent.activeFocus ? UserModel.currentUser.accentColor : Style.menuBorder
color: Style.backgroundColor
}
@ -55,10 +48,10 @@ Item {
onClicked: root.sendReplyMessage()
icon {
source: "image://svgimage-custom-color/send.svg" + "/" + Style.ncBlue
source: "image://svgimage-custom-color/send.svg" + "/" + Style.menuBorder
width: 38
height: 38
color: hovered || !sendReplyMessageButton.enabled? Style.menuBorder : Style.ncBlue
color: hovered || !sendReplyMessageButton.enabled? Style.menuBorder : UserModel.currentUser.accentColor
}
anchors {

View File

@ -113,7 +113,6 @@ public:
QString conversationToken;
QString messageId;
QString messageSent;
bool displayReplyOption = false;
};
Type _type;

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
*
* This program is free software; you can redistribute it and/or modify
@ -80,7 +80,6 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
roles[TalkNotificationConversationTokenRole] = "conversationToken";
roles[TalkNotificationMessageIdRole] = "messageId";
roles[TalkNotificationMessageSentRole] = "messageSent";
roles[TalkNotificationDisplayReplyOptionRole] = "displayReplyOption";
return roles;
}
@ -333,8 +332,6 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
return a._talkNotificationData.messageId;
case TalkNotificationMessageSentRole:
return replyMessageSent(a);
case TalkNotificationDisplayReplyOptionRole:
return displayReplyOption(a);
default:
return QVariant();
}
@ -614,12 +611,6 @@ void ActivityListModel::slotTriggerAction(const int activityIndex, const int act
const auto action = activity._links[actionIndex];
// TODO this will change with https://github.com/nextcloud/desktop/issues/4159
if (action._verb == "WEB" && action._label == tr("View chat")) {
setDisplayReplyOption(activityIndex);
return;
}
if (action._verb == "WEB") {
Utility::openBrowser(QUrl(action._link));
return;
@ -672,7 +663,7 @@ QVariantList ActivityListModel::convertLinksToActionButtons(const Activity &acti
}
if (static_cast<quint32>(activity._links.size()) > maxActionButtons()) {
customList << ActivityListModel::convertLinkToActionButton(activity, activity._links.first());
customList << ActivityListModel::convertLinkToActionButton(activity._links.first());
return customList;
}
@ -680,20 +671,18 @@ QVariantList ActivityListModel::convertLinksToActionButtons(const Activity &acti
if (activityLink._verb == QStringLiteral("DELETE")
|| (activity._objectType == QStringLiteral("chat") || activity._objectType == QStringLiteral("call")
|| activity._objectType == QStringLiteral("room"))) {
customList << ActivityListModel::convertLinkToActionButton(activity, activityLink);
customList << ActivityListModel::convertLinkToActionButton(activityLink);
}
}
return customList;
}
QVariant ActivityListModel::convertLinkToActionButton(const OCC::Activity &activity, const OCC::ActivityLink &activityLink)
QVariant ActivityListModel::convertLinkToActionButton(const OCC::ActivityLink &activityLink)
{
auto activityLinkCopy = activityLink;
const auto isReplyIconApplicable = activityLink._verb == QStringLiteral("WEB")
&& (activity._objectType == QStringLiteral("chat") || activity._objectType == QStringLiteral("call")
|| activity._objectType == QStringLiteral("room"));
const auto isReplyIconApplicable = activityLink._verb == QStringLiteral("REPLY");
const QString replyButtonPath = QStringLiteral("image://svgimage-custom-color/reply.svg");
@ -704,14 +693,8 @@ QVariant ActivityListModel::convertLinkToActionButton(const OCC::Activity &activ
QString(replyButtonPath + "/" + OCC::Theme::instance()->wizardHeaderTitleColor().name());
}
const auto isReplyLabelApplicable = activityLink._verb == QStringLiteral("WEB")
&& (activity._objectType == QStringLiteral("chat")
|| (activity._objectType != QStringLiteral("room") && activity._objectType != QStringLiteral("call")));
if (activityLink._verb == QStringLiteral("DELETE")) {
activityLinkCopy._label = QObject::tr("Mark as read");
} else if (isReplyLabelApplicable) {
activityLinkCopy._label = QObject::tr("Reply");
}
return QVariant::fromValue(activityLinkCopy);
@ -836,15 +819,4 @@ QString ActivityListModel::replyMessageSent(const Activity &activity) const
{
return activity._talkNotificationData.messageSent;
}
void ActivityListModel::setDisplayReplyOption(const int activityIndex)
{
_finalList[activityIndex]._talkNotificationData.displayReplyOption = true;
emit dataChanged(index(activityIndex, 0), index(activityIndex, 0), {ActivityListModel::TalkNotificationDisplayReplyOptionRole});
}
bool ActivityListModel::displayReplyOption(const Activity &activity) const
{
return activity._talkNotificationData.displayReplyOption;
}
}

View File

@ -71,7 +71,6 @@ public:
TalkNotificationConversationTokenRole,
TalkNotificationMessageIdRole,
TalkNotificationMessageSentRole,
TalkNotificationDisplayReplyOptionRole,
};
Q_ENUM(DataRole)
@ -108,7 +107,6 @@ public:
void setReplyMessageSent(const int activityIndex, const QString &message);
QString replyMessageSent(const Activity &activity) const;
bool displayReplyOption(const Activity &activity) const;
public slots:
void slotRefreshActivity();
@ -143,14 +141,12 @@ protected:
private:
static QVariantList convertLinksToMenuEntries(const Activity &activity);
static QVariantList convertLinksToActionButtons(const Activity &activity);
static QVariant convertLinkToActionButton(const Activity &activity, const ActivityLink &activityLink);
static QVariant convertLinkToActionButton(const ActivityLink &activityLink);
void combineActivityLists();
bool canFetchActivities() const;
void ingestActivities(const QJsonArray &activities);
void setDisplayReplyOption(const int activityIndex);
ActivityList _activityLists;
ActivityList _syncFileItemLists;
ActivityList _notificationLists;
@ -175,7 +171,7 @@ private:
bool _doneFetching = false;
bool _hideOldActivities = true;
static constexpr quint32 MaxActionButtons = 2;
static constexpr quint32 MaxActionButtons = 3;
};
}

View File

@ -98,9 +98,9 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
a._id = json.value("notification_id").toInt();
// 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") {
// 1. server == 24 & has Talk: object_type is chat/call/room & object_id contains conversationToken/messageId
// 2. server < 24 & has Talk: object_type is chat/call/room & object_id contains _only_ conversationToken
if (a._objectType == "chat" || a._objectType == "call" || a._objectType == "room") {
const auto objectId = json.value("object_id").toString();
const auto objectIdData = objectId.split("/");
a._talkNotificationData.conversationToken = objectIdData.first();
@ -109,6 +109,14 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
} else {
qCInfo(lcServerNotification) << "Replying directly to Talk conversation" << a._talkNotificationData.conversationToken << "will not be possible because the notification doesn't contain the message ID.";
}
ActivityLink al;
al._label = tr("Reply");
al._verb = "REPLY";
al._primary = true;
a._links.insert(0, al);
list.append(a);
}
a._status = 0;

View File

@ -797,7 +797,7 @@ void User::slotSendReplyMessage(const int activityIndex, const QString &token, c
{
QPointer<TalkReply> talkReply = new TalkReply(_account.data(), this);
talkReply->sendReplyMessage(token, message, replyTo);
connect(talkReply, &TalkReply::replyMessageSent, this, [&](const QString &message) {
connect(talkReply, &TalkReply::replyMessageSent, this, [&, activityIndex](const QString &message) {
_activityModel->setReplyMessageSent(activityIndex, message);
});
}

View File

@ -139,11 +139,18 @@ public:
QJsonArray actionsArray;
QJsonObject replyAction;
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
replyAction.insert(QStringLiteral("primary"), true);
actionsArray.push_back(replyAction);
QJsonObject primaryAction;
primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
primaryAction.insert(QStringLiteral("primary"), true);
primaryAction.insert(QStringLiteral("primary"), false);
actionsArray.push_back(primaryAction);
QJsonObject secondaryAction;
@ -185,11 +192,18 @@ public:
QJsonArray actionsArray;
QJsonObject replyAction;
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
replyAction.insert(QStringLiteral("primary"), true);
actionsArray.push_back(replyAction);
QJsonObject primaryAction;
primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
primaryAction.insert(QStringLiteral("primary"), true);
primaryAction.insert(QStringLiteral("primary"), false);
actionsArray.push_back(primaryAction);
QJsonObject secondaryAction;
@ -222,11 +236,18 @@ public:
QJsonArray actionsArray;
QJsonObject replyAction;
replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
replyAction.insert(QStringLiteral("primary"), true);
actionsArray.push_back(replyAction);
QJsonObject primaryAction;
primaryAction.insert(QStringLiteral("label"), QStringLiteral("Call back"));
primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
primaryAction.insert(QStringLiteral("primary"), true);
primaryAction.insert(QStringLiteral("primary"), false);
actionsArray.push_back(primaryAction);
QJsonObject secondaryAction;
@ -612,6 +633,10 @@ private slots:
QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationConversationTokenRole).canConvert<QString>());
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageIdRole).canConvert<QString>());
QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageSentRole).canConvert<QString>());
// Unfortunately, trying to check anything relating to filepaths causes a crash
// when the folder manager is invoked by the model to look for the relevant file
}
@ -658,10 +683,10 @@ private slots:
const auto actionButtonsLinks =
index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
// both action links and buttons must contain a "WEB" verb element at the beginning
QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("WEB"));
QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("WEB"));
// both action links and buttons must contain a "REPLY" verb element at the beginning
QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
// the first action button for chat must have image set
QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSource.isEmpty());
QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
@ -687,7 +712,7 @@ private slots:
}
} else if ((objectType == QStringLiteral("call"))) {
QVERIFY(
actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
actionButtonsLinks[1].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
}
} else {
QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Dismiss"));