diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 4a9a565c..ab067eee 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -49,7 +49,7 @@ Rectangle { smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height - source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100.0 : 25.0) + ((avatar.crop) ? "" : "&scale")) : "" + source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100 : 25) + ((avatar.crop) ? "" : "&scale")) : "" MouseArea { id: mouseArea diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index b84b4c36..a0009174 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -16,12 +16,13 @@ Page { property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) property bool collapsed: false -Component { + Component { id: roomDirectoryComponent RoomDirectory { } - } + + } ListView { id: roomlist @@ -570,10 +571,10 @@ Component { ToolTip.visible: hovered ToolTip.text: qsTr("Room directory") Layout.margins: Nheko.paddingMedium - onClicked: { + onClicked: { var win = roomDirectoryComponent.createObject(timelineRoot); win.show(); - } + } } ImageButton { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 491a336f..92cd431a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -186,7 +186,16 @@ ApplicationWindow { ComboBox { enabled: roomSettings.canChangeJoinRules - model: [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")] + model: { + let opts = [qsTr("Anyone and guests"), qsTr("Anyone"), qsTr("Invited users")]; + if (roomSettings.supportsKnocking) + opts.push(qsTr("By knocking")); + + if (roomSettings.supportsRestricted) + opts.push(qsTr("Restricted by membership in other rooms")); + + return opts; + } currentIndex: roomSettings.accessJoinRules onActivated: { roomSettings.changeAccessRules(index); diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index a8bdf183..893edc77 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -3,6 +3,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later import QtQuick 2.6 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 import im.nheko 1.0 Item { @@ -357,11 +359,23 @@ Item { DelegateChoice { roleValue: MtxEvent.Member - NoticeMessage { - body: formatted - isOnlyEmoji: false - isReply: d.isReply - formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) + ColumnLayout { + width: parent ? parent.width : undefined + + NoticeMessage { + body: formatted + isOnlyEmoji: false + isReply: d.isReply + formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) + } + + Button { + visible: d.relatedEventCacheBuster, room.showAcceptKnockButton(d.eventId) + palette: Nheko.colors + text: qsTr("Allow them in") + onClicked: room.acceptKnock(eventId) + } + } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 88d575fa..1e369b46 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1689,6 +1689,19 @@ TimelineModel::formatJoinRuleEvent(QString id) return tr("%1 opened the room to the public.").arg(name); case mtx::events::state::JoinRule::Invite: return tr("%1 made this room require and invitation to join.").arg(name); + case mtx::events::state::JoinRule::Knock: + return tr("%1 allowed to join this room by knocking.").arg(name); + case mtx::events::state::JoinRule::Restricted: { + QStringList rooms; + for (const auto &r : event->content.allow) { + if (r.type == mtx::events::state::JoinAllowanceType::RoomMembership) + rooms.push_back(QString::fromStdString(r.room_id)); + } + return tr("%1 allowed members of the following rooms to automatically join this " + "room: %2") + .arg(name) + .arg(rooms.join(", ")); + } default: // Currently, knock and private are reserved keywords and not implemented in Matrix. return ""; @@ -1771,6 +1784,51 @@ TimelineModel::formatPowerLevelEvent(QString id) return tr("%1 has changed the room's permissions.").arg(name); } +void +TimelineModel::acceptKnock(QString id) +{ + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return; + + auto event = std::get_if>(e); + if (!event) + return; + + if (!permissions_.canInvite()) + return; + + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return; + + using namespace mtx::events::state; + if (event->content.membership != Membership::Knock) + return; + + ChatPage::instance()->inviteUser(QString::fromStdString(event->state_key), ""); +} + +bool +TimelineModel::showAcceptKnockButton(QString id) +{ + mtx::events::collections::TimelineEvents *e = events.get(id.toStdString(), ""); + if (!e) + return false; + + auto event = std::get_if>(e); + if (!event) + return false; + + if (!permissions_.canInvite()) + return false; + + if (cache::isRoomMember(event->state_key, room_id_.toStdString())) + return false; + + using namespace mtx::events::state; + return event->content.membership == Membership::Knock; +} + QString TimelineModel::formatMemberEvent(QString id) { @@ -1826,7 +1884,13 @@ TimelineModel::formatMemberEvent(QString id) // the case of nothing changed but join follows join shouldn't happen, so // just show it as join } else { - rendered = tr("%1 joined.").arg(name); + if (event->content.join_authorised_via_users_server.empty()) + rendered = tr("%1 joined.").arg(name); + else + rendered = tr("%1 joined via authorisation from %2's server.") + .arg(name) + .arg(QString::fromStdString( + event->content.join_authorised_via_users_server)); } break; case Membership::Leave: diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index aa07fe01..e3ca8811 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -238,6 +238,8 @@ public: Q_INVOKABLE QString avatarUrl(QString id) const; Q_INVOKABLE QString formatDateSeparator(QDate date) const; Q_INVOKABLE QString formatTypingUsers(const std::vector &users, QColor bg); + Q_INVOKABLE bool showAcceptKnockButton(QString id); + Q_INVOKABLE void acceptKnock(QString id); Q_INVOKABLE QString formatMemberEvent(QString id); Q_INVOKABLE QString formatJoinRuleEvent(QString id); Q_INVOKABLE QString formatHistoryVisibilityEvent(QString id); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index fcba8205..2fb93325 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -218,8 +218,12 @@ RoomSettings::RoomSettings(QString roomid, QObject *parent) } else { accessRules_ = 1; } - } else { + } else if (info_.join_rule == state::JoinRule::Invite) { accessRules_ = 2; + } else if (info_.join_rule == state::JoinRule::Knock) { + accessRules_ = 3; + } else if (info_.join_rule == state::JoinRule::Restricted) { + accessRules_ = 4; } emit accessJoinRulesChanged(); } @@ -368,6 +372,21 @@ RoomSettings::isEncryptionEnabled() const return usesEncryption_; } +bool +RoomSettings::supportsKnocking() const +{ + return info_.version != "" && info_.version != "1" && info_.version != "2" && + info_.version != "3" && info_.version != "4" && info_.version != "5" && + info_.version != "6"; +} +bool +RoomSettings::supportsRestricted() const +{ + return info_.version != "" && info_.version != "1" && info_.version != "2" && + info_.version != "3" && info_.version != "4" && info_.version != "5" && + info_.version != "6" && info_.version != "7"; +} + void RoomSettings::openEditModal() { @@ -464,6 +483,15 @@ RoomSettings::changeAccessRules(int index) case 1: event.join_rule = state::JoinRule::Public; break; + case 2: + event.join_rule = state::JoinRule::Invite; + break; + case 3: + event.join_rule = state::JoinRule::Knock; + break; + case 4: + event.join_rule = state::JoinRule::Restricted; + break; default: event.join_rule = state::JoinRule::Invite; } diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 1c8b47d6..ab768ffe 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -78,6 +78,8 @@ class RoomSettings : public QObject Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT) Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) + Q_PROPERTY(bool supportsKnocking READ supportsKnocking CONSTANT) + Q_PROPERTY(bool supportsRestricted READ supportsRestricted CONSTANT) public: RoomSettings(QString roomid, QObject *parent = nullptr); @@ -98,6 +100,8 @@ public: //! Whether the user has enough power level to send m.room.avatar event. bool canChangeAvatar() const; bool isEncryptionEnabled() const; + bool supportsKnocking() const; + bool supportsRestricted() const; Q_INVOKABLE void enableEncryption(); Q_INVOKABLE void updateAvatar();