From e2765212fb229e8d025d2255314a04a376207749 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 28 May 2021 17:25:46 +0200 Subject: [PATCH] Reimplement room context menus --- resources/qml/RoomList.qml | 132 ++++++++++++++++++------ resources/qml/delegates/TextMessage.qml | 2 +- resources/qml/dialogs/InputDialog.qml | 53 ++++++++++ resources/res.qrc | 1 + src/timeline/RoomlistModel.cpp | 72 +++++++++++++ src/timeline/RoomlistModel.h | 6 ++ 6 files changed, 232 insertions(+), 34 deletions(-) create mode 100644 resources/qml/dialogs/InputDialog.qml diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index e9bb351f..b184aef0 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./dialogs" import Qt.labs.platform 1.1 as Platform import QtQuick 2.13 import QtQuick.Controls 2.13 @@ -31,6 +32,93 @@ Page { target: TimelineManager } + Platform.Menu { + id: roomContextMenu + + property string roomid + property var tags + + function show(roomid_, tags_) { + roomid = roomid_; + tags = tags_; + roomContextMenu.clear(); + roomContextMenu.addItem(leaveOpt.createObject(roomContextMenu)); + roomContextMenu.addItem(separatorOpt.createObject(roomContextMenu)); + for (let tag of Rooms.tags()) { + roomContextMenu.addItem(tagDelegate.createObject(roomContextMenu, { + "t": tag + })); + } + roomContextMenu.addItem(newTagOpt.createObject(roomContextMenu)); + open(); + } + + InputDialog { + id: newTag + + title: qsTr("New tag") + prompt: qsTr("Enter the tag you want to use:") + onAccepted: function(text) { + Rooms.toggleTag(roomContextMenu.roomid, "u." + text, true); + } + } + + Component { + id: leaveOpt + + Platform.MenuItem { + text: qsTr("Leave room") + onTriggered: Rooms.leave(roomContextMenu.roomid) + } + + } + + Component { + id: separatorOpt + + Platform.MenuSeparator { + text: qsTr("Tag room as:") + } + + } + + Component { + id: tagDelegate + + Platform.MenuItem { + property string t + + text: { + switch (t) { + case "m.favourite": + return qsTr("Favourite"); + case "m.lowpriority": + return qsTr("Low priority"); + case "m.server_notice": + return qsTr("Server notice"); + default: + return t.substring(2); + } + } + checkable: true + checked: roomContextMenu.tags.includes(t) + onTriggered: Rooms.toggleTag(roomContextMenu.roomid, t, checked) + } + + } + + Component { + id: newTagOpt + + Platform.MenuItem { + text: qsTr("Create new tag...") + onTriggered: newTag.show() + } + + } + + } + delegate: Rectangle { id: roomItem @@ -75,6 +163,12 @@ Page { } ] + TapHandler { + acceptedButtons: Qt.RightButton + onSingleTapped: roomContextMenu.show(model.roomId, model.tags) + gesturePolicy: TapHandler.ReleaseWithinBounds + } + HoverHandler { id: hovered } @@ -94,6 +188,7 @@ Page { id: avatar + enabled: false Layout.alignment: Qt.AlignVCenter height: Math.ceil(fontMetrics.lineSpacing * 2.3) width: Math.ceil(fontMetrics.lineSpacing * 2.3) @@ -279,43 +374,14 @@ Page { Layout.preferredHeight: userInfoGrid.implicitHeight + 2 * Nheko.paddingMedium Layout.minimumHeight: 40 - ApplicationWindow { + InputDialog { id: statusDialog - modality: Qt.NonModal - flags: Qt.Dialog title: qsTr("Status Message") - width: 350 - height: fontMetrics.lineSpacing * 7 - - ColumnLayout { - anchors.margins: Nheko.paddingLarge - anchors.fill: parent - - Label { - color: Nheko.colors.text - text: qsTr("Enter your status message:") - } - - MatrixTextField { - id: statusInput - - Layout.fillWidth: true - } - + prompt: qsTr("Enter your status message:") + onAccepted: function(text) { + Nheko.setStatusMessage(text); } - - footer: DialogButtonBox { - standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel - onAccepted: { - Nheko.setStatusMessage(statusInput.text); - statusDialog.close(); - } - onRejected: { - statusDialog.close(); - } - } - } Platform.Menu { diff --git a/resources/qml/delegates/TextMessage.qml b/resources/qml/delegates/TextMessage.qml index ae622480..f65eda79 100644 --- a/resources/qml/delegates/TextMessage.qml +++ b/resources/qml/delegates/TextMessage.qml @@ -9,7 +9,7 @@ MatrixText { property string formatted: model.data.formattedBody property string copyText: selectedText ? getText(selectionStart, selectionEnd) : model.data.body - text: "" + formatted.replace("
", "
")
+    text: "" + formatted.replace("
", "
").replace("", "").replace("", "").replace("", "").replace("", "")
     width: parent ? parent.width : undefined
     height: isReply ? Math.round(Math.min(timelineView.height / 8, implicitHeight)) : undefined
     clip: isReply
diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml
new file mode 100644
index 00000000..0cd6be1c
--- /dev/null
+++ b/resources/qml/dialogs/InputDialog.qml
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.13
+import QtQuick.Controls 2.13
+import QtQuick.Layouts 1.3
+import im.nheko 1.0
+
+ApplicationWindow {
+    id: inputDialog
+
+    property alias prompt: promptLabel.text
+    property var onAccepted: undefined
+
+    modality: Qt.NonModal
+    flags: Qt.Dialog
+    width: 350
+    height: fontMetrics.lineSpacing * 7
+
+    ColumnLayout {
+        anchors.margins: Nheko.paddingLarge
+        anchors.fill: parent
+
+        Label {
+            id: promptLabel
+
+            color: Nheko.colors.text
+        }
+
+        MatrixTextField {
+            id: statusInput
+
+            Layout.fillWidth: true
+        }
+
+    }
+
+    footer: DialogButtonBox {
+        standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel
+        onAccepted: {
+            if (inputDialog.onAccepted)
+                inputDialog.onAccepted(statusInput.text);
+
+            inputDialog.close();
+        }
+        onRejected: {
+            inputDialog.close();
+        }
+    }
+
+}
diff --git a/resources/res.qrc b/resources/res.qrc
index 79e63810..183cf394 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -168,6 +168,7 @@
         qml/device-verification/NewVerificationRequest.qml
         qml/device-verification/Failed.qml
         qml/device-verification/Success.qml
+        qml/dialogs/InputDialog.qml
         qml/ui/Ripple.qml
         qml/voip/ActiveCallBar.qml
         qml/voip/CallDevices.qml
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index f3d4dad7..63054aa9 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -49,6 +49,7 @@ RoomlistModel::roleNames() const
           {NotificationCount, "notificationCount"},
           {IsInvite, "isInvite"},
           {IsSpace, "isSpace"},
+          {Tags, "tags"},
         };
 }
 
@@ -84,6 +85,13 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                         case Roles::IsInvite:
                         case Roles::IsSpace:
                                 return false;
+                        case Roles::Tags: {
+                                auto info = cache::singleRoomInfo(roomid.toStdString());
+                                QStringList list;
+                                for (const auto &t : info.tags)
+                                        list.push_back(QString::fromStdString(t));
+                                return list;
+                        }
                         default:
                                 return {};
                         }
@@ -111,6 +119,8 @@ RoomlistModel::data(const QModelIndex &index, int role) const
                                 return true;
                         case Roles::IsSpace:
                                 return false;
+                        case Roles::Tags:
+                                return QStringList();
                         default:
                                 return {};
                         }
@@ -364,6 +374,21 @@ RoomlistModel::declineInvite(QString roomid)
                 }
         }
 }
+void
+RoomlistModel::leave(QString roomid)
+{
+        if (models.contains(roomid)) {
+                auto idx = roomidToIndex(roomid);
+
+                if (idx != -1) {
+                        beginRemoveRows(QModelIndex(), idx, idx);
+                        roomids.erase(roomids.begin() + idx);
+                        models.remove(roomid);
+                        endRemoveRows();
+                        ChatPage::instance()->leaveRoom(roomid);
+                }
+        }
+}
 
 namespace {
 enum NotificationImportance : short
@@ -440,3 +465,50 @@ FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *pare
 
         sort(0);
 }
+
+QStringList
+FilteredRoomlistModel::tags()
+{
+        std::set ts;
+        for (const auto &e : cache::roomInfo()) {
+                for (const auto &t : e.tags) {
+                        if (t.find("u.") == 0) {
+                                ts.insert(t);
+                        }
+                }
+        }
+
+        QStringList ret{{
+          "m.favourite",
+          "m.lowpriority",
+        }};
+
+        for (const auto &t : ts)
+                ret.push_back(QString::fromStdString(t));
+
+        return ret;
+}
+
+void
+FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on)
+{
+        if (on) {
+                http::client()->put_tag(
+                  roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) {
+                          if (err) {
+                                  nhlog::ui()->error("Failed to add tag: {}, {}",
+                                                     tag.toStdString(),
+                                                     err->matrix_error.error);
+                          }
+                  });
+        } else {
+                http::client()->delete_tag(
+                  roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) {
+                          if (err) {
+                                  nhlog::ui()->error("Failed to delete tag: {}, {}",
+                                                     tag.toStdString(),
+                                                     err->matrix_error.error);
+                          }
+                  });
+        }
+}
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index ff85614c..2d1e5264 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -10,6 +10,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 
@@ -33,6 +34,7 @@ public:
                 NotificationCount,
                 IsInvite,
                 IsSpace,
+                Tags,
         };
 
         RoomlistModel(TimelineViewManager *parent = nullptr);
@@ -66,6 +68,7 @@ public slots:
         }
         void acceptInvite(QString roomid);
         void declineInvite(QString roomid);
+        void leave(QString roomid);
 
 private slots:
         void updateReadStatus(const std::map roomReadStatus_);
@@ -100,6 +103,9 @@ public slots:
         }
         void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); }
         void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); }
+        void leave(QString roomid) { roomlistmodel->leave(roomid); }
+        QStringList tags();
+        void toggleTag(QString roomid, QString tag, bool on);
 
 private:
         short int calculateImportance(const QModelIndex &idx) const;