diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4004d850..136505ab 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -388,6 +388,8 @@ set(SRC_FILES
src/ui/NhekoGlobalObject.h
src/ui/RoomSettings.cpp
src/ui/RoomSettings.h
+ src/ui/RoomSummary.cpp
+ src/ui/RoomSummary.h
src/ui/Theme.cpp
src/ui/Theme.h
src/ui/ThemeManager.cpp
@@ -581,7 +583,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
- GIT_TAG v0.8.0
+ GIT_TAG b706492de042455630063c847574bbc5ed5d4641
)
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 2c2979a9..d6d877d7 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -203,8 +203,8 @@ modules:
buildsystem: cmake-ninja
name: mtxclient
sources:
- - commit: 2a1cf15cbda4d3deb7986c9f3b38e6c7aabb0d6f
- tag: v0.8.0
+ - commit: b706492de042455630063c847574bbc5ed5d4641
+ #tag: v0.8.0
type: git
url: https://github.com/Nheko-Reborn/mtxclient.git
- config-opts:
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 7cc41db9..fc321136 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -174,6 +174,14 @@ Pane {
}
+ Component {
+ id: confirmJoinRoomDialog
+
+ ConfirmJoinRoomDialog {
+ }
+
+ }
+
Component {
id: leaveRoomComponent
@@ -241,6 +249,12 @@ Pane {
destroyOnClose(dialog);
}
+ function onShowRoomJoinPrompt(summary) {
+ var dialog = confirmJoinRoomDialog.createObject(timelineRoot, {"summary": summary});
+ dialog.show();
+ destroyOnClose(dialog);
+ }
+
target: Nheko
}
diff --git a/resources/qml/dialogs/ConfirmJoinRoomDialog.qml b/resources/qml/dialogs/ConfirmJoinRoomDialog.qml
new file mode 100644
index 00000000..91f03dcf
--- /dev/null
+++ b/resources/qml/dialogs/ConfirmJoinRoomDialog.qml
@@ -0,0 +1,151 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import "../ui"
+import Qt.labs.platform 1.1 as Platform
+import QtQuick 2.15
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.2
+import QtQuick.Window 2.13
+import im.nheko 1.0
+
+ApplicationWindow {
+ id: joinRoomRoot
+
+ required property RoomSummary summary
+
+ title: qsTr("Confirm room join")
+ modality: Qt.WindowModal
+ flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
+ palette: Nheko.colors
+ color: Nheko.colors.window
+ width: 350
+ height: content.implicitHeight + Nheko.paddingLarge + footer.implicitHeight
+
+ Shortcut {
+ sequence: StandardKey.Cancel
+ onActivated: dbb.rejected()
+ }
+
+ ColumnLayout {
+ id: content
+ spacing: Nheko.paddingMedium
+ anchors.margins: Nheko.paddingMedium
+ anchors.fill: parent
+
+ Avatar {
+ Layout.topMargin: Nheko.paddingMedium
+ url: summary.roomAvatarUrl.replace("mxc://", "image://MxcImage/")
+ roomid: summary.roomid
+ displayName: summary.roomName
+ height: 130
+ width: 130
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ Spinner {
+ Layout.alignment: Qt.AlignHCenter
+ visible: !summary.isLoaded
+ foreground: Nheko.colors.mid
+ running: !summary.isLoaded
+ }
+
+ TextEdit {
+ readOnly: true
+ textFormat: TextEdit.RichText
+ text: summary.roomName
+ font.pixelSize: fontMetrics.font.pixelSize * 2
+ color: Nheko.colors.text
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ horizontalAlignment: TextEdit.AlignHCenter
+ wrapMode: TextEdit.Wrap
+ selectByMouse: true
+ }
+ TextEdit {
+ readOnly: true
+ textFormat: TextEdit.RichText
+ text: summary.roomid
+ font.pixelSize: fontMetrics.font.pixelSize * 0.8
+ color: Nheko.colors.text
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ horizontalAlignment: TextEdit.AlignHCenter
+ wrapMode: TextEdit.Wrap
+ selectByMouse: true
+ }
+ RowLayout {
+ spacing: Nheko.paddingMedium
+ Layout.alignment: Qt.AlignHCenter
+
+ MatrixText {
+ text: qsTr("%n member(s)", "", summary.memberCount)
+ }
+
+ ImageButton {
+ image: ":/icons/icons/ui/people.svg"
+ enabled: false
+ }
+
+ }
+ TextEdit {
+ readOnly: true
+ textFormat: TextEdit.RichText
+ text: summary.roomTopic
+ color: Nheko.colors.text
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.fillWidth: true
+ horizontalAlignment: TextEdit.AlignHCenter
+ wrapMode: TextEdit.Wrap
+ selectByMouse: true
+ }
+
+ Label {
+ id: promptLabel
+
+ text: summary.isKnockOnly ? qsTr("This room can't be joined directly. You can however knock on the room and room members can accept or decline this join request. You can additionally provide a reason for them to let you in below:") : qsTr("Do you want to join this room? You can optionally add a reason below:")
+ color: Nheko.colors.text
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.Wrap
+ font.bold: true
+ }
+
+ MatrixTextField {
+ id: reason
+
+ focus: true
+ Layout.fillWidth: true
+ text: joinRoomRoot.summary.reason
+ }
+
+ }
+
+ footer: DialogButtonBox {
+ id: dbb
+
+ standardButtons: DialogButtonBox.Cancel
+ onAccepted: {
+ summary.reason = reason.text;
+ summary.join();
+ joinRoomRoot.close();
+ }
+ onRejected: {
+ joinRoomRoot.close();
+ }
+
+ Button {
+ text: summary.isKnockOnly ? qsTr("Knock") : qsTr("Join")
+ enabled: input.text.match("#.+?:.{3,}")
+ DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
+ }
+
+ }
+
+}
diff --git a/resources/qml/dialogs/JoinRoomDialog.qml b/resources/qml/dialogs/JoinRoomDialog.qml
index 263481aa..0098370d 100644
--- a/resources/qml/dialogs/JoinRoomDialog.qml
+++ b/resources/qml/dialogs/JoinRoomDialog.qml
@@ -64,7 +64,7 @@ ApplicationWindow {
}
Button {
- text: "Join"
+ text: qsTr("Join")
enabled: input.text.match("#.+?:.{3,}")
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
diff --git a/resources/res.qrc b/resources/res.qrc
index 7f08c29d..4bdb3cb8 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -150,6 +150,7 @@
qml/device-verification/Success.qml
qml/device-verification/Waiting.qml
qml/dialogs/AliasEditor.qml
+ qml/dialogs/ConfirmJoinRoomDialog.qml
qml/dialogs/CreateDirect.qml
qml/dialogs/CreateRoom.qml
qml/dialogs/HiddenEventsDialog.qml
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index baab28d1..d7fbfefa 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -22,6 +22,7 @@
#include "Utils.h"
#include "encryption/DeviceVerificationFlow.h"
#include "encryption/Olm.h"
+#include "ui/RoomSummary.h"
#include "ui/Theme.h"
#include "ui/UserProfile.h"
#include "voip/CallManager.h"
@@ -130,7 +131,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QObject *parent)
connect(this,
&ChatPage::internalKnock,
this,
- qOverload &, QString, bool>(
+ qOverload &, QString, bool, bool>(
&ChatPage::knockRoom),
Qt::QueuedConnection);
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
@@ -697,23 +698,26 @@ void
ChatPage::knockRoom(const QString &room,
const std::vector &via,
QString reason,
- bool failedJoin)
+ bool failedJoin,
+ bool promptForConfirmation)
{
const auto room_id = room.toStdString();
bool confirmed = false;
- reason = QInputDialog::getText(
- nullptr,
- tr("Knock on room"),
- // clang-format off
+ if (promptForConfirmation) {
+ reason = QInputDialog::getText(
+ nullptr,
+ tr("Knock on room"),
+ // clang-format off
failedJoin
? tr("You failed to join %1. You can try to knock, so that others can invite you in. Do you want to do so?\nYou may optionally provide a reason for others to accept your knock:").arg(room)
: tr("Do you really want to knock on %1? You may optionally provide a reason for others to accept your knock:").arg(room),
- // clang-format on
- QLineEdit::Normal,
- reason,
- &confirmed);
- if (!confirmed) {
- return;
+ // clang-format on
+ QLineEdit::Normal,
+ reason,
+ &confirmed);
+ if (!confirmed) {
+ return;
+ }
}
http::client()->knock_room(
@@ -742,13 +746,12 @@ ChatPage::joinRoomVia(const std::string &room_id,
bool promptForConfirmation,
const QString &reason)
{
- if (promptForConfirmation &&
- QMessageBox::Yes !=
- QMessageBox::question(
- nullptr,
- tr("Confirm join"),
- tr("Do you really want to join %1?").arg(QString::fromStdString(room_id))))
+ if (promptForConfirmation) {
+ auto prompt = new RoomSummary(room_id, via, reason);
+ QQmlEngine::setObjectOwnership(prompt, QQmlEngine::JavaScriptOwnership);
+ emit showRoomJoinPrompt(prompt);
return;
+ }
http::client()->join_room(
room_id,
@@ -756,7 +759,7 @@ ChatPage::joinRoomVia(const std::string &room_id,
[this, room_id, reason, via](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
if (err) {
if (err->matrix_error.errcode == mtx::errors::ErrorCode::M_FORBIDDEN)
- emit internalKnock(QString::fromStdString(room_id), via, reason, true);
+ emit internalKnock(QString::fromStdString(room_id), via, reason, true, true);
else
emit showNotification(tr("Failed to join room: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
diff --git a/src/ChatPage.h b/src/ChatPage.h
index af06f02c..c0f0b559 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -24,6 +24,7 @@
#include "CacheCryptoStructs.h"
#include "CacheStructs.h"
+#include "ui/RoomSummary.h"
class TimelineViewManager;
class UserSettings;
@@ -84,8 +85,9 @@ public slots:
void knockRoom(const QString &room, QString reason = "") { knockRoom(room, {}, reason, false); }
void knockRoom(const QString &room,
const std::vector &via,
- QString reason = "",
- bool failedJoin = false);
+ QString reason = "",
+ bool failedJoin = false,
+ bool promptForConfirmation = true);
void joinRoomVia(const std::string &room_id,
const std::vector &via,
bool promptForConfirmation = true,
@@ -163,10 +165,12 @@ signals:
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
+ void showRoomJoinPrompt(RoomSummary *);
void internalKnock(const QString &room,
const std::vector &via,
- QString reason = "",
- bool failedJoin = false);
+ QString reason = "",
+ bool failedJoin = false,
+ bool promptForConfirmation = true);
private slots:
void logout();
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index d2e28277..e5032fb2 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -54,6 +54,7 @@
#include "ui/NhekoDropArea.h"
#include "ui/NhekoEventObserver.h"
#include "ui/NhekoGlobalObject.h"
+#include "ui/RoomSummary.h"
#include "ui/UIA.h"
#include "voip/CallManager.h"
#include "voip/WebRTCSession.h"
@@ -65,6 +66,7 @@
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
Q_DECLARE_METATYPE(std::vector)
Q_DECLARE_METATYPE(std::vector)
+Q_DECLARE_METATYPE(mtx::responses::PublicRoom)
MainWindow *MainWindow::instance_ = nullptr;
@@ -142,6 +144,7 @@ MainWindow::registerQmlTypes()
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
+ qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType();
qRegisterMetaType>();
@@ -180,6 +183,12 @@ MainWindow::registerQmlTypes()
qmlRegisterType("im.nheko", 1, 0, "Login");
qmlRegisterType("im.nheko", 1, 0, "Registration");
qmlRegisterType("im.nheko", 1, 0, "HiddenEvents");
+ qmlRegisterUncreatableType(
+ "im.nheko",
+ 1,
+ 0,
+ "RoomSummary",
+ QStringLiteral("Please use joinRoom to create a room summary."));
qmlRegisterUncreatableType(
"im.nheko",
1,
diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp
index 2e1aadf0..5891f8cd 100644
--- a/src/ui/NhekoGlobalObject.cpp
+++ b/src/ui/NhekoGlobalObject.cpp
@@ -22,6 +22,7 @@ Nheko::Nheko()
connect(
UserSettings::instance().get(), &UserSettings::themeChanged, this, &Nheko::colorsChanged);
connect(ChatPage::instance(), &ChatPage::contentLoaded, this, &Nheko::updateUserProfile);
+ connect(ChatPage::instance(), &ChatPage::showRoomJoinPrompt, this, &Nheko::showRoomJoinPrompt);
connect(this, &Nheko::joinRoom, ChatPage::instance(), &ChatPage::joinRoom);
}
diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h
index f9de489d..c8c6f667 100644
--- a/src/ui/NhekoGlobalObject.h
+++ b/src/ui/NhekoGlobalObject.h
@@ -11,6 +11,7 @@
#include "AliasEditModel.h"
#include "PowerlevelsEditModels.h"
+#include "RoomSummary.h"
#include "Theme.h"
#include "UserProfile.h"
@@ -76,6 +77,8 @@ signals:
void openJoinRoomDialog();
void joinRoom(QString roomId, QString reason = "");
+ void showRoomJoinPrompt(RoomSummary *summary);
+
private:
QScopedPointer currentUser_;
};
diff --git a/src/ui/RoomSummary.cpp b/src/ui/RoomSummary.cpp
new file mode 100644
index 00000000..dc035fe0
--- /dev/null
+++ b/src/ui/RoomSummary.cpp
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "RoomSummary.h"
+
+#include
+
+#include "ChatPage.h"
+#include "MatrixClient.h"
+
+RoomSummary::RoomSummary(std::string roomIdOrAlias_,
+ std::vector vias_,
+ QString r_,
+ QObject *p)
+ : QObject(p)
+ , roomIdOrAlias(std::move(roomIdOrAlias_))
+ , vias(std::move(vias_))
+ , reason_(std::move(r_))
+{
+ auto ctx = std::make_shared();
+
+ connect(ctx.get(), &RoomSummaryProxy::failed, this, [this]() {
+ loaded_ = true;
+ emit loaded();
+ });
+ connect(
+ ctx.get(), &RoomSummaryProxy::loaded, this, [this](const mtx::responses::PublicRoom &resp) {
+ loaded_ = true;
+ room = resp;
+ emit loaded();
+ });
+
+ http::client()->get_summary(
+ roomIdOrAlias,
+ [proxy = std::move(ctx)](const mtx::responses::PublicRoom &room, mtx::http::RequestErr e) {
+ if (e) {
+ emit proxy->failed();
+ } else {
+ emit proxy->loaded(room);
+ }
+ },
+ vias);
+}
+
+void
+RoomSummary::join()
+{
+ if (isKnockOnly())
+ ChatPage::instance()->knockRoom(
+ QString::fromStdString(roomIdOrAlias), vias, reason_, false, false);
+ else
+ ChatPage::instance()->joinRoomVia(roomIdOrAlias, vias, false, reason_);
+}
diff --git a/src/ui/RoomSummary.h b/src/ui/RoomSummary.h
new file mode 100644
index 00000000..8d6e0e23
--- /dev/null
+++ b/src/ui/RoomSummary.h
@@ -0,0 +1,86 @@
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include
+
+#include
+
+#include
+
+class RoomSummaryProxy : public QObject
+{
+ Q_OBJECT
+
+public:
+ RoomSummaryProxy() {}
+signals:
+ void loaded(mtx::responses::PublicRoom room);
+ void failed();
+};
+
+class RoomSummary : public QObject
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString reason READ reason WRITE setReason NOTIFY reasonChanged)
+
+ Q_PROPERTY(QString roomid READ roomid NOTIFY loaded)
+ Q_PROPERTY(QString roomName READ roomName NOTIFY loaded)
+ Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY loaded)
+ Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY loaded)
+ Q_PROPERTY(bool isInvite READ isInvite NOTIFY loaded)
+ Q_PROPERTY(bool isSpace READ isInvite NOTIFY loaded)
+ Q_PROPERTY(bool isKnockOnly READ isKnockOnly NOTIFY loaded)
+ Q_PROPERTY(bool isLoaded READ isLoaded NOTIFY loaded)
+ Q_PROPERTY(int memberCount READ memberCount NOTIFY loaded)
+
+public:
+ explicit RoomSummary(std::string roomIdOrAlias_,
+ std::vector vias_,
+ QString reason_,
+ QObject *p = nullptr);
+
+ void setReason(const QString &r)
+ {
+ reason_ = r;
+ emit reasonChanged();
+ }
+ QString reason() const { return reason_; }
+
+ QString roomid() const { return room ? QString::fromStdString(room->room_id) : ""; }
+ QString roomName() const
+ {
+ return QString::fromStdString(room ? room->room_id : roomIdOrAlias);
+ }
+ QString roomTopic() const { return room ? QString::fromStdString(room->topic) : ""; }
+ QString roomAvatarUrl() const { return room ? QString::fromStdString(room->avatar_url) : ""; }
+ bool isInvite() const
+ {
+ return room && room->membership == mtx::events::state::Membership::Invite;
+ }
+ bool isSpace() const { return room && room->room_type == mtx::events::state::room_type::space; }
+ int memberCount() const { return room ? room->num_joined_members : 0; }
+ bool isKnockOnly() const
+ {
+ return room && (room->join_rule == mtx::events::state::JoinRule::Knock ||
+ room->join_rule == mtx::events::state::JoinRule::KnockRestricted);
+ }
+
+ bool isLoaded() const { return room.has_value() || loaded_; }
+
+ Q_INVOKABLE void join();
+
+signals:
+ void loaded();
+ void reasonChanged();
+
+private:
+ std::string roomIdOrAlias;
+ std::vector vias;
+ std::optional room;
+ QString reason_;
+ bool loaded_ = false;
+};