From 19f2c02eda12290c2e227086bd6ebc4b3284375c Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Thu, 15 Jul 2021 18:08:59 -0400 Subject: Remove 'respond to key requests' functionality --- resources/qml/RoomSettings.qml | 17 ----------------- src/Olm.cpp | 5 ++--- src/Utils.cpp | 16 ---------------- src/Utils.h | 6 ------ src/ui/RoomSettings.cpp | 6 ------ src/ui/RoomSettings.h | 2 -- 6 files changed, 2 insertions(+), 50 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index c852b837..4b06401a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -222,23 +222,6 @@ ApplicationWindow { buttons: Dialog.Ok | Dialog.Cancel } - MatrixText { - visible: roomSettings.isEncryptionEnabled - text: qsTr("Respond to key requests") - } - - ToggleButton { - visible: roomSettings.isEncryptionEnabled - ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed.") - checked: roomSettings.respondsToKeyRequests - onClicked: { - roomSettings.changeKeyRequestsPreference(checked); - } - Layout.alignment: Qt.AlignRight - } - Item { // for adding extra space between sections Layout.fillWidth: true diff --git a/src/Olm.cpp b/src/Olm.cpp index ff4c883b..4a403cd0 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -882,13 +882,12 @@ handle_key_request_message(const mtx::events::DeviceEventdebug("ignoring key request for room {}", req.content.room_id); return; } - if (verifiedDevice || utils::respondsToKeyRequests(req.content.room_id)) { + if (verifiedDevice) { // share the minimum index we have minimumIndex = -1; } diff --git a/src/Utils.cpp b/src/Utils.cpp index 8d5ae4a9..eabf50d9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -172,22 +172,6 @@ utils::scaleFactor() return settings.value("settings/scale_factor", -1).toFloat(); } -bool -utils::respondsToKeyRequests(const std::string &roomId) -{ - return respondsToKeyRequests(QString::fromStdString(roomId)); -} - -bool -utils::respondsToKeyRequests(const QString &roomId) -{ - if (roomId.isEmpty()) - return false; - - QSettings settings; - return settings.value("rooms/respond_to_key_requests/" + roomId, false).toBool(); -} - void utils::setKeyRequestsPreference(QString roomId, bool value) { diff --git a/src/Utils.h b/src/Utils.h index 1d48e2c7..a50681ca 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -67,12 +67,6 @@ scaleFactor(); void setScaleFactor(float factor); -//! Whether or not we should respond to key requests for the given room. -bool -respondsToKeyRequests(const QString &roomId); -bool -respondsToKeyRequests(const std::string &roomId); - void setKeyRequestsPreference(QString roomId, bool value); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index f78ef09b..ae5fce0c 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -291,12 +291,6 @@ RoomSettings::accessJoinRules() return accessRules_; } -bool -RoomSettings::respondsToKeyRequests() -{ - return usesEncryption_ && utils::respondsToKeyRequests(roomid_); -} - void RoomSettings::changeKeyRequestsPreference(bool isOn) { diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 367f3111..2a68a182 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -78,7 +78,6 @@ 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 respondsToKeyRequests READ respondsToKeyRequests NOTIFY keyRequestsChanged) public: RoomSettings(QString roomid, QObject *parent = nullptr); @@ -91,7 +90,6 @@ public: int memberCount() const; int notifications(); int accessJoinRules(); - bool respondsToKeyRequests(); bool isLoading() const; //! Whether the user has enough power level to send m.room.join_rules events. bool canChangeJoinRules() const; -- cgit 1.5.1 From 88ed0fade74915e83df3a8e335d7cc49ee068d5c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 20 Jul 2021 14:09:19 +0200 Subject: Explicitly reload data in delegates, if related events got loaded --- resources/qml/MessageView.qml | 2 ++ resources/qml/TimelineRow.qml | 39 ++++++++++++++++------------- resources/qml/delegates/MessageDelegate.qml | 11 ++++---- resources/qml/delegates/Reply.qml | 2 ++ resources/qml/emoji/StickerPicker.qml | 1 - src/timeline/TimelineModel.cpp | 4 +++ src/timeline/TimelineModel.h | 3 +++ 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index f56af237..564c8dc7 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -341,6 +341,7 @@ ScrollView { required property var timestamp required property int status required property int index + required property int relatedEventCacheBuster required property string previousMessageUserId required property string day required property string previousMessageDay @@ -446,6 +447,7 @@ ScrollView { trustlevel: wrapper.trustlevel timestamp: wrapper.timestamp status: wrapper.status + relatedEventCacheBuster: wrapper.relatedEventCacheBuster y: section.visible && section.active ? section.y + section.height : 0 HoverHandler { diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 70db08e7..755ab503 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -40,6 +40,7 @@ Item { required property int trustlevel required property var timestamp required property int status + required property int relatedEventCacheBuster anchors.left: parent.left anchors.right: parent.right @@ -90,25 +91,26 @@ Item { } visible: replyTo - userColor: replyTo, TimelineManager.userColor(userId, Nheko.colors.base) - blurhash: replyTo, fromModel(Room.Blurhash) ?? "" - body: replyTo, fromModel(Room.Body) ?? "" - formattedBody: replyTo, fromModel(Room.FormattedBody) ?? "" + userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base) + blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? "" + body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? "" + formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? "" eventId: fromModel(Room.EventId) ?? "" - filename: replyTo, fromModel(Room.Filename) ?? "" - filesize: replyTo, fromModel(Room.Filesize) ?? "" - proportionalHeight: replyTo, fromModel(Room.ProportionalHeight) ?? 1 - type: replyTo, fromModel(Room.Type) ?? MtxEvent.UnknownMessage - typeString: replyTo, fromModel(Room.TypeString) ?? "" - url: replyTo, fromModel(Room.Url) ?? "" - originalWidth: replyTo, fromModel(Room.OriginalWidth) ?? 0 - isOnlyEmoji: replyTo, fromModel(Room.IsOnlyEmoji) ?? false - userId: replyTo, fromModel(Room.UserId) ?? "" - userName: replyTo, fromModel(Room.UserName) ?? "" - thumbnailUrl: replyTo, fromModel(Room.ThumbnailUrl) ?? "" - roomTopic: replyTo, fromModel(Room.RoomTopic) ?? "" - roomName: replyTo, fromModel(Room.RoomName) ?? "" - callType: replyTo, fromModel(Room.CallType) ?? "" + filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? "" + filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? "" + proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1 + type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage + typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? "" + url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? "" + originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0 + isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false + userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? "" + userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? "" + thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? "" + roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" + roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" + callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? "" + relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 } // actual message content @@ -134,6 +136,7 @@ Item { roomTopic: r.roomTopic roomName: r.roomName callType: r.callType + relatedEventCacheBuster: r.relatedEventCacheBuster isReply: false } diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index 0b060629..c64ae887 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -29,6 +29,7 @@ Item { required property string roomTopic required property string roomName required property string callType + required property int relatedEventCacheBuster height: chooser.childrenRect.height @@ -301,7 +302,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatPowerLevelEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatPowerLevelEvent(d.eventId) } } @@ -313,7 +314,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatJoinRuleEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatJoinRuleEvent(d.eventId) } } @@ -325,7 +326,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatHistoryVisibilityEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatHistoryVisibilityEvent(d.eventId) } } @@ -337,7 +338,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatGuestAccessEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatGuestAccessEvent(d.eventId) } } @@ -349,7 +350,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: room.formatMemberEvent(d.eventId) + formatted: d.relatedEventCacheBuster, room.formatMemberEvent(d.eventId) } } diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 3a188d78..75e3d617 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -30,6 +30,7 @@ Item { property string roomTopic property string roomName property string callType + property int relatedEventCacheBuster width: parent.width height: replyContainer.height @@ -95,6 +96,7 @@ Item { roomTopic: r.roomTopic roomName: r.roomName callType: r.callType + relatedEventCacheBuster: r.relatedEventCacheBuster enabled: false width: parent.width isReply: true diff --git a/resources/qml/emoji/StickerPicker.qml b/resources/qml/emoji/StickerPicker.qml index 813c0b12..3731a948 100644 --- a/resources/qml/emoji/StickerPicker.qml +++ b/resources/qml/emoji/StickerPicker.qml @@ -122,7 +122,6 @@ Menu { id: gridView model: roomid ? TimelineManager.completerFor("stickers", roomid) : null - Layout.preferredHeight: cellHeight * 3.5 Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 Layout.leftMargin: 4 diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index abfe28a9..7b3f0729 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -344,6 +344,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj &EventStore::dataChanged, this, [this](int from, int to) { + relatedEventCacheBuster++; nhlog::ui()->debug( "data changed {} to {}", events.size() - to - 1, events.size() - from - 1); emit dataChanged(index(events.size() - to - 1, 0), @@ -443,6 +444,7 @@ TimelineModel::roleNames() const {RoomTopic, "roomTopic"}, {CallType, "callType"}, {Dump, "dump"}, + {RelatedEventCacheBuster, "relatedEventCacheBuster"}, }; } int @@ -676,6 +678,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r return QVariant(m); } + case RelatedEventCacheBuster: + return relatedEventCacheBuster; default: return QVariant(); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0e2895d4..3c80ade8 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -208,6 +208,7 @@ public: RoomTopic, CallType, Dump, + RelatedEventCacheBuster, }; Q_ENUM(Roles); @@ -400,6 +401,8 @@ private: int notification_count = 0, highlight_count = 0; + unsigned int relatedEventCacheBuster = 0; + bool decryptDescription = true; bool m_paginationInProgress = false; bool isSpace_ = false; -- cgit 1.5.1 From 77a0c574bfc962b6c37426fb16a70ca16c08a3f5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 29 May 2021 21:09:21 -0400 Subject: QML the room member list --- CMakeLists.txt | 4 +- resources/qml/RoomMembers.qml | 111 ++++++++++++++++++++++++++ resources/qml/TopBar.qml | 2 +- resources/res.qrc | 1 + src/MainWindow.cpp | 10 +-- src/MainWindow.h | 1 - src/MemberList.cpp | 91 ++++++++++++++++++++++ src/MemberList.h | 58 ++++++++++++++ src/dialogs/MemberList.cpp | 146 ----------------------------------- src/dialogs/MemberList.h | 57 -------------- src/main.cpp | 3 + src/timeline/TimelineModel.cpp | 12 ++- src/timeline/TimelineModel.h | 5 +- src/timeline/TimelineViewManager.cpp | 7 +- src/timeline/TimelineViewManager.h | 1 - 15 files changed, 284 insertions(+), 225 deletions(-) create mode 100644 resources/qml/RoomMembers.qml create mode 100644 src/MemberList.cpp create mode 100644 src/MemberList.h delete mode 100644 src/dialogs/MemberList.cpp delete mode 100644 src/dialogs/MemberList.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b26b2e5..84f52766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -285,7 +285,6 @@ set(SRC_FILES src/dialogs/JoinRoom.cpp src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp - src/dialogs/MemberList.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp @@ -351,6 +350,7 @@ set(SRC_FILES src/LoginPage.cpp src/MainWindow.cpp src/MatrixClient.cpp + src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp src/RegisterPage.cpp @@ -496,7 +496,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/JoinRoom.h src/dialogs/LeaveRoom.h src/dialogs/Logout.h - src/dialogs/MemberList.h src/dialogs/PreviewUploadOverlay.h src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h @@ -557,6 +556,7 @@ qt5_wrap_cpp(MOC_HEADERS src/InviteeItem.h src/LoginPage.h src/MainWindow.h + src/MemberList.h src/MxcImageProvider.h src/RegisterPage.h src/SSOHandler.h diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml new file mode 100644 index 00000000..4406c1b0 --- /dev/null +++ b/resources/qml/RoomMembers.qml @@ -0,0 +1,111 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Window 2.12 +import im.nheko 1.0 + +ApplicationWindow { + id: roomMembersRoot + + property string roomName: Rooms.currentRoom.roomName + property MemberList members + + title: qsTr("Members of ") + roomName + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 650 + width: 420 + minimumHeight: 420 + + Shortcut { + sequence: StandardKey.Cancel + onActivated: roomMembersRoot.close() + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Avatar { + id: roomAvatar + + width: 130 + height: width + displayName: members.roomName + Layout.alignment: Qt.AlignHCenter + url: members.avatarUrl.replace("mxc://", "image://MxcImage/") + onClicked: TimelineManager.timeline.openRoomSettings(members.roomId) + } + + Label { + font.pixelSize: 24 + text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + roomName + Layout.alignment: Qt.AlignHCenter + } + + ScrollView { + clip: false + palette: colors + padding: 10 + ScrollBar.horizontal.visible: false + Layout.fillHeight: true + Layout.minimumHeight: 200 + Layout.fillWidth: true + + ListView { + id: memberList + + clip: true + spacing: 8 + boundsBehavior: Flickable.StopAtBounds + model: members + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + delegate: RowLayout { + spacing: 10 + + Avatar { + width: avatarSize + height: avatarSize + userid: model.mxid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.displayName + onClicked: TimelineManager.timeline.openUserProfile(model.mxid) + } + + ColumnLayout { + spacing: 5 + + Label { + text: model.displayName + color: TimelineManager.userColor(model ? model.mxid : "", colors.window) + font.pointSize: 12 + } + + Label { + text: model.mxid + color: colors.buttonText + font.pointSize: 10 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } + } + } + } + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + onAccepted: roomMembersRoot.close() + } +} diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 58aba0c7..50c2447c 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -116,7 +116,7 @@ Rectangle { Platform.MenuItem { text: qsTr("Members") - onTriggered: TimelineManager.openMemberListDialog(room.roomId()) + onTriggered: Rooms.currentRoom.openRoomMembers(room.roomId()) } Platform.MenuItem { diff --git a/resources/res.qrc b/resources/res.qrc index e9479e57..da5288c8 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -185,6 +185,7 @@ qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml qml/components/FlatButton.qml + qml/RoomMembers.qml media/ring.ogg diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ed337ca4..36bada83 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -21,6 +21,7 @@ #include "LoginPage.h" #include "MainWindow.h" #include "MatrixClient.h" +#include "MemberList.h" #include "RegisterPage.h" #include "TrayIcon.h" #include "UserSettingsPage.h" @@ -36,7 +37,6 @@ #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" #include "dialogs/Logout.h" -#include "dialogs/MemberList.h" #include "dialogs/ReadReceipts.h" MainWindow *MainWindow::instance_ = nullptr; @@ -310,14 +310,6 @@ MainWindow::hasActiveUser() settings.contains(prefix + "auth/user_id"); } -void -MainWindow::openMemberListDialog(const QString &room_id) -{ - auto dialog = new dialogs::MemberList(room_id, this); - - showDialog(dialog); -} - void MainWindow::openLeaveRoomDialog(const QString &room_id) { diff --git a/src/MainWindow.h b/src/MainWindow.h index 3571f079..6d62545c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -65,7 +65,6 @@ public: std::function callback); void openJoinRoomDialog(std::function callback); void openLogoutDialog(); - void openMemberListDialog(const QString &room_id); void openReadReceiptsDialog(const QString &event_id); void hideOverlay(); diff --git a/src/MemberList.cpp b/src/MemberList.cpp new file mode 100644 index 00000000..62488277 --- /dev/null +++ b/src/MemberList.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MemberList.h" + +#include "Cache.h" +#include "ChatPage.h" +#include "Config.h" +#include "Logging.h" +#include "Utils.h" +#include "timeline/TimelineViewManager.h" +#include "ui/Avatar.h" + +MemberList::MemberList(const QString &room_id, QWidget *parent) + : QAbstractListModel{parent} + , room_id_{room_id} +{ + try { + info_ = cache::singleRoomInfo(room_id_.toStdString()); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", + room_id_.toStdString()); + } + + try { + addUsers(cache::getMembers(room_id_.toStdString())); + } catch (const lmdb::error &e) { + nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); + } +} + +void +MemberList::addUsers(const std::vector &members) +{ + beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); + + for (const auto &member : members) + m_memberList.push_back( + {member, + ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl( + member.user_id)}); + + endInsertRows(); +} + +QHash +MemberList::roleNames() const +{ + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; +} + +QVariant +MemberList::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return m_memberList[index.row()].first.user_id; + case DisplayName: + return m_memberList[index.row()].first.display_name; + case AvatarUrl: + return m_memberList[index.row()].second; + default: + return {}; + } +} + +bool MemberList::canFetchMore(const QModelIndex &) const +{ + const size_t numMembers = rowCount(); + return (numMembers > 1 && numMembers < info_.member_count); +} + +void +MemberList::fetchMore(const QModelIndex &) +{ + addUsers(cache::getMembers(room_id_.toStdString(), rowCount())); +} diff --git a/src/MemberList.h b/src/MemberList.h new file mode 100644 index 00000000..dbe69f4b --- /dev/null +++ b/src/MemberList.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "CacheStructs.h" +#include + +class MemberList : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(size_t memberCount READ memberCount NOTIFY memberCountChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; + MemberList(const QString &room_id, QWidget *parent = nullptr); + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + Q_UNUSED(parent) + return static_cast(m_memberList.size()); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QString roomName() const { return QString::fromStdString(info_.name); } + size_t memberCount() const { return info_.member_count; } + QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } + QString roomId() const { return room_id_; } + +signals: + void roomNameChanged(); + void memberCountChanged(); + void avatarUrlChanged(); + void roomIdChanged(); + +public slots: + void addUsers(const std::vector &users); + +protected: + bool canFetchMore(const QModelIndex &) const; + void fetchMore(const QModelIndex &); + +private: + QVector> m_memberList; + QString room_id_; + RoomInfo info_; +}; diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp deleted file mode 100644 index 21eb72b0..00000000 --- a/src/dialogs/MemberList.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dialogs/MemberList.h" - -#include "Cache.h" -#include "ChatPage.h" -#include "Config.h" -#include "Logging.h" -#include "Utils.h" -#include "ui/Avatar.h" - -using namespace dialogs; - -MemberItem::MemberItem(const RoomMember &member, QWidget *parent) - : QWidget(parent) -{ - topLayout_ = new QHBoxLayout(this); - topLayout_->setMargin(0); - - textLayout_ = new QVBoxLayout; - textLayout_->setMargin(0); - textLayout_->setSpacing(0); - - avatar_ = new Avatar(this, 44); - avatar_->setLetter(utils::firstChar(member.display_name)); - - avatar_->setImage(ChatPage::instance()->currentRoom(), member.user_id); - - QFont nameFont; - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); - - userId_ = new QLabel(member.user_id, this); - userName_ = new QLabel(member.display_name, this); - userName_->setFont(nameFont); - - textLayout_->addWidget(userName_); - textLayout_->addWidget(userId_); - - topLayout_->addWidget(avatar_); - topLayout_->addLayout(textLayout_, 1); -} - -void -MemberItem::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -MemberList::MemberList(const QString &room_id, QWidget *parent) - : QFrame(parent) - , room_id_{room_id} -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - list_ = new QListWidget; - list_->setFrameStyle(QFrame::NoFrame); - list_->setSelectionMode(QAbstractItemView::NoSelection); - list_->setSpacing(5); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); - - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - setMinimumHeight(list_->sizeHint().height() * 2); - setMinimumWidth(std::max(list_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN, - QFontMetrics(largeFont).averageCharWidth() * 30 - - 2 * conf::modals::WIDGET_MARGIN)); - - QFont font; - font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); - - topLabel_ = new QLabel(tr("Room members"), this); - topLabel_->setAlignment(Qt::AlignCenter); - topLabel_->setFont(font); - - auto okBtn = new QPushButton(tr("OK"), this); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(15); - buttonLayout->addStretch(1); - buttonLayout->addWidget(okBtn); - - layout->addWidget(topLabel_); - layout->addWidget(list_); - layout->addLayout(buttonLayout); - - list_->clear(); - - connect(list_->verticalScrollBar(), &QAbstractSlider::valueChanged, this, [this](int pos) { - if (pos != list_->verticalScrollBar()->maximum()) - return; - - const size_t numMembers = list_->count() - 1; - - if (numMembers > 0) - addUsers(cache::getMembers(room_id_.toStdString(), numMembers)); - }); - - try { - addUsers(cache::getMembers(room_id_.toStdString())); - } catch (const lmdb::error &e) { - nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); - } - - auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); - connect(closeShortcut, &QShortcut::activated, this, &MemberList::close); - connect(okBtn, &QPushButton::clicked, this, &MemberList::close); -} - -void -MemberList::addUsers(const std::vector &members) -{ - for (const auto &member : members) { - auto user = new MemberItem(member, this); - auto item = new QListWidgetItem; - - item->setSizeHint(user->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - list_->insertItem(list_->count() - 1, item); - list_->setItemWidget(item, user); - } -} diff --git a/src/dialogs/MemberList.h b/src/dialogs/MemberList.h deleted file mode 100644 index b822eec8..00000000 --- a/src/dialogs/MemberList.h +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class Avatar; -class QPushButton; -class QHBoxLayout; -class QLabel; -class QVBoxLayout; - -struct RoomMember; - -template -class QSharedPointer; - -namespace dialogs { - -class MemberItem : public QWidget -{ - Q_OBJECT - -public: - MemberItem(const RoomMember &member, QWidget *parent); - -protected: - void paintEvent(QPaintEvent *) override; - -private: - QHBoxLayout *topLayout_; - QVBoxLayout *textLayout_; - - Avatar *avatar_; - - QLabel *userName_; - QLabel *userId_; -}; - -class MemberList : public QFrame -{ - Q_OBJECT -public: - MemberList(const QString &room_id, QWidget *parent = nullptr); - -public slots: - void addUsers(const std::vector &users); - -private: - QString room_id_; - QLabel *topLabel_; - QListWidget *list_; -}; -} // dialogs diff --git a/src/main.cpp b/src/main.cpp index 29e93d49..376fc4f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -205,6 +205,9 @@ main(int argc, char *argv[]) parser.process(app); + // make sure that size_t properties will work + qRegisterMetaType("size_t"); + app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"})); http::init(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 7b3f0729..48d69493 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -25,6 +25,7 @@ #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" +#include "MemberList.h" #include "MxcImageProvider.h" #include "Olm.h" #include "TimelineViewManager.h" @@ -1061,9 +1062,16 @@ TimelineModel::openUserProfile(QString userid) } void -TimelineModel::openRoomSettings() +TimelineModel::openRoomMembers() { - RoomSettings *settings = new RoomSettings(roomId(), this); + MemberList *memberList = new MemberList(roomId()); + emit openRoomMembersDialog(memberList); +} + +void +TimelineModel::openRoomSettings(QString room_id) +{ + RoomSettings *settings = new RoomSettings(room_id == QString() ? roomId() : room_id, this); connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); openRoomSettingsDialog(settings); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 3c80ade8..feb7b5f5 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -17,6 +17,7 @@ #include "CacheStructs.h" #include "EventStore.h" #include "InputBar.h" +#include "MemberList.h" #include "Permissions.h" #include "ui/RoomSettings.h" #include "ui/UserProfile.h" @@ -235,7 +236,8 @@ public: Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid); - Q_INVOKABLE void openRoomSettings(); + Q_INVOKABLE void openRoomMembers(); + Q_INVOKABLE void openRoomSettings(QString room_id = QString()); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; @@ -352,6 +354,7 @@ signals: void lastMessageChanged(); void notificationsChanged(); + void openRoomMembersDialog(MemberList *members); void openRoomSettingsDialog(RoomSettings *settings); void newMessageToSend(mtx::events::collections::TimelineEvents event); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 3e69f92b..011ff61c 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -174,6 +174,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -428,11 +430,6 @@ TimelineViewManager::openInviteUsersDialog() [this](const QStringList &invitees) { emit inviteUsers(invitees); }); } void -TimelineViewManager::openMemberListDialog(QString roomid) const -{ - MainWindow::instance()->openMemberListDialog(roomid); -} -void TimelineViewManager::openLeaveRoomDialog(QString roomid) const { MainWindow::instance()->openLeaveRoomDialog(roomid); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 15b4f523..39d0d31c 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -66,7 +66,6 @@ public: Q_INVOKABLE void focusMessageInput(); Q_INVOKABLE void openInviteUsersDialog(); - Q_INVOKABLE void openMemberListDialog(QString roomid) const; Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); -- cgit 1.5.1 From 6c57fa6c5b491e981958e417458edac40e9000b4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 10 Jun 2021 20:11:49 -0400 Subject: QML the invite dialog This also adds a property `roomId` to TimelineModel. --- CMakeLists.txt | 4 - resources/qml/InviteDialog.qml | 112 +++++++++++++++++++++++++ resources/qml/TopBar.qml | 14 +++- resources/qml/types/Invitee.qml | 5 ++ resources/res.qrc | 13 +-- src/ChatPage.cpp | 28 +++++++ src/InviteeItem.cpp | 28 ------- src/InviteeItem.h | 31 ------- src/MainWindow.cpp | 13 --- src/dialogs/InviteUsers.cpp | 158 ----------------------------------- src/dialogs/InviteUsers.h | 45 ---------- src/timeline/TimelineModel.h | 1 + src/timeline/TimelineViewManager.cpp | 51 +++++++++++ src/timeline/TimelineViewManager.h | 5 +- 14 files changed, 215 insertions(+), 293 deletions(-) create mode 100644 resources/qml/InviteDialog.qml create mode 100644 resources/qml/types/Invitee.qml delete mode 100644 src/InviteeItem.cpp delete mode 100644 src/InviteeItem.h delete mode 100644 src/dialogs/InviteUsers.cpp delete mode 100644 src/dialogs/InviteUsers.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 84f52766..56592950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,7 +281,6 @@ set(SRC_FILES src/dialogs/CreateRoom.cpp src/dialogs/FallbackAuth.cpp src/dialogs/ImageOverlay.cpp - src/dialogs/InviteUsers.cpp src/dialogs/JoinRoom.cpp src/dialogs/LeaveRoom.cpp src/dialogs/Logout.cpp @@ -345,7 +344,6 @@ set(SRC_FILES src/CompletionProxyModel.cpp src/DeviceVerificationFlow.cpp src/EventAccessors.cpp - src/InviteeItem.cpp src/Logging.cpp src/LoginPage.cpp src/MainWindow.cpp @@ -492,7 +490,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/CreateRoom.h src/dialogs/FallbackAuth.h src/dialogs/ImageOverlay.h - src/dialogs/InviteUsers.h src/dialogs/JoinRoom.h src/dialogs/LeaveRoom.h src/dialogs/Logout.h @@ -553,7 +550,6 @@ qt5_wrap_cpp(MOC_HEADERS src/Clipboard.h src/CompletionProxyModel.h src/DeviceVerificationFlow.h - src/InviteeItem.h src/LoginPage.h src/MainWindow.h src/MemberList.h diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml new file mode 100644 index 00000000..5d3a8f1e --- /dev/null +++ b/resources/qml/InviteDialog.qml @@ -0,0 +1,112 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import im.nheko 1.0 +import "./types" + +ApplicationWindow { + id: inviteDialogRoot + + property string roomId + property string roomName + property list invitees + + function addInvite() { + if (inviteeEntry.text.match("@.+?:.{3,}")) + { + invitees.push(inviteeComponent.createObject( + inviteDialogRoot, { + "invitee": inviteeEntry.text + })); + inviteeEntry.clear(); + } + } + + function accept() { + if (inviteeEntry.text !== "") + addInvite(); + + var inviteeStringList = ["temp"]; // the "temp" element exists to declare this as a string array + for (var i = 0; i < invitees.length; ++i) + inviteeStringList.push(invitees[i].invitee); + inviteeStringList.shift(); // remove the first item + + TimelineManager.inviteUsers(inviteDialogRoot.roomId, inviteeStringList); + } + + title: qsTr("Invite users to ") + roomName + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 380 + width: 340 + + Component { + id: inviteeComponent + + Invitee {} + } + + // TODO: make this work in the TextField + Shortcut { + sequence: "Ctrl+Enter" + onActivated: inviteDialogRoot.accept() + } + + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Label { + text: qsTr("User ID to invite") + Layout.fillWidth: true + } + + RowLayout { + spacing: 10 + + TextField { + id: inviteeEntry + + placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") + Layout.fillWidth: true + onAccepted: if (text !== "") addInvite() + } + + Button { + text: qsTr("Invite") + onClicked: if (inviteeEntry.text !== "") addInvite() + } + } + + ListView { + id: inviteesList + + Layout.fillWidth: true + Layout.fillHeight: true + model: invitees + delegate: Label { + text: model.invitee + } + } + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Invite") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + onClicked: { + inviteDialogRoot.accept(); + inviteDialogRoot.close(); + } + } + + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole + onClicked: inviteDialogRoot.close(); + } + } +} diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 50c2447c..72dbe604 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -21,6 +21,12 @@ Rectangle { z: 3 color: Nheko.colors.window + Component { + id: inviteDialog + + InviteDialog {} + } + TapHandler { onSingleTapped: { if (room) @@ -111,7 +117,13 @@ Rectangle { Platform.MenuItem { visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") - onTriggered: TimelineManager.openInviteUsersDialog() + onTriggered: { + var dialog = inviteDialog.createObject(topBar, { + "roomId": room.roomId, + "roomName": room.roomName + }); + dialog.show(); + } } Platform.MenuItem { diff --git a/resources/qml/types/Invitee.qml b/resources/qml/types/Invitee.qml new file mode 100644 index 00000000..fbc0b781 --- /dev/null +++ b/resources/qml/types/Invitee.qml @@ -0,0 +1,5 @@ +import QtQuick 2.12 + +Item { + property string invitee +} diff --git a/resources/res.qrc b/resources/res.qrc index da5288c8..ad7b6665 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -9,7 +9,6 @@ icons/ui/do-not-disturb-rounded-sign@2x.png icons/ui/round-remove-button.png icons/ui/round-remove-button@2x.png - icons/ui/double-tick-indicator.png icons/ui/double-tick-indicator@2x.png icons/ui/lock.png @@ -55,22 +54,17 @@ icons/ui/pause-symbol@2x.png icons/ui/remove-symbol.png icons/ui/remove-symbol@2x.png - icons/ui/world.png icons/ui/world@2x.png - icons/ui/tag.png icons/ui/tag@2x.png icons/ui/star.png icons/ui/star@2x.png icons/ui/lowprio.png icons/ui/lowprio@2x.png - icons/ui/edit.png icons/ui/edit@2x.png - icons/ui/mail-reply.png - icons/ui/place-call.png icons/ui/end-call.png icons/ui/microphone-mute.png @@ -78,7 +72,6 @@ icons/ui/screen-share.png icons/ui/toggle-camera-view.png icons/ui/video-call.png - icons/emoji-categories/people.png icons/emoji-categories/people@2x.png icons/emoji-categories/nature.png @@ -99,16 +92,12 @@ nheko.png nheko.svg - splash.png splash@2x.png - register.png register@2x.png - login.png login@2x.png - nheko-512.png nheko-256.png nheko-128.png @@ -186,6 +175,8 @@ qml/components/AdaptiveLayoutElement.qml qml/components/FlatButton.qml qml/RoomMembers.qml + qml/InviteDialog.qml + qml/types/Invitee.qml media/ring.ogg diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 10a91557..f6ea4539 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -140,6 +140,34 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) } }); + connect( + view_manager_, &TimelineViewManager::showRoomList, splitter, &Splitter::showFullRoomList); + connect( + view_manager_, + &TimelineViewManager::inviteUsers, + this, + [this](QString roomId, QStringList users) { + for (int ii = 0; ii < users.size(); ++ii) { + QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() { + const auto user = users.at(ii); + + http::client()->invite_user( + roomId.toStdString(), + user.toStdString(), + [this, user](const mtx::responses::RoomInvite &, + mtx::http::RequestErr err) { + if (err) { + emit showNotification( + tr("Failed to invite user: %1").arg(user)); + return; + } + + emit showNotification(tr("Invited user: %1").arg(user)); + }); + }); + } + }); + connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); diff --git a/src/InviteeItem.cpp b/src/InviteeItem.cpp deleted file mode 100644 index 27f02560..00000000 --- a/src/InviteeItem.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "InviteeItem.h" - -constexpr int SidePadding = 10; - -InviteeItem::InviteeItem(mtx::identifiers::User user, QWidget *parent) - : QWidget{parent} - , user_{QString::fromStdString(user.to_string())} -{ - auto topLayout_ = new QHBoxLayout(this); - topLayout_->setSpacing(0); - topLayout_->setContentsMargins(SidePadding, 0, 3 * SidePadding, 0); - - name_ = new QLabel(user_, this); - removeUserBtn_ = new QPushButton(tr("Remove"), this); - - topLayout_->addWidget(name_); - topLayout_->addWidget(removeUserBtn_, 0, Qt::AlignRight); - - connect(removeUserBtn_, &QPushButton::clicked, this, &InviteeItem::removeItem); -} diff --git a/src/InviteeItem.h b/src/InviteeItem.h deleted file mode 100644 index 014541ea..00000000 --- a/src/InviteeItem.h +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include - -class QPushButton; -class QLabel; - -class InviteeItem : public QWidget -{ - Q_OBJECT - -public: - InviteeItem(mtx::identifiers::User user, QWidget *parent = nullptr); - - QString userID() { return user_; } - -signals: - void removeItem(); - -private: - QString user_; - - QLabel *name_; - QPushButton *removeUserBtn_; -}; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 36bada83..c0486d01 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -33,7 +33,6 @@ #include "ui/SnackBar.h" #include "dialogs/CreateRoom.h" -#include "dialogs/InviteUsers.h" #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" #include "dialogs/Logout.h" @@ -333,18 +332,6 @@ MainWindow::showOverlayProgressBar() showSolidOverlayModal(spinner_); } -void -MainWindow::openInviteUsersDialog(std::function callback) -{ - auto dialog = new dialogs::InviteUsers(this); - connect(dialog, &dialogs::InviteUsers::sendInvites, this, [callback](QStringList invitees) { - if (!invitees.isEmpty()) - callback(invitees); - }); - - showDialog(dialog); -} - void MainWindow::openJoinRoomDialog(std::function callback) { diff --git a/src/dialogs/InviteUsers.cpp b/src/dialogs/InviteUsers.cpp deleted file mode 100644 index 9dd6085f..00000000 --- a/src/dialogs/InviteUsers.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dialogs/InviteUsers.h" - -#include "Config.h" -#include "InviteeItem.h" -#include "ui/TextField.h" - -#include - -using namespace dialogs; - -InviteUsers::InviteUsers(QWidget *parent) - : QFrame(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - setMinimumWidth(conf::window::minModalWidth); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(0); - buttonLayout->setMargin(0); - - confirmBtn_ = new QPushButton("Invite", this); - confirmBtn_->setDefault(true); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - - buttonLayout->addStretch(1); - buttonLayout->setSpacing(15); - buttonLayout->addWidget(cancelBtn_); - buttonLayout->addWidget(confirmBtn_); - - inviteeInput_ = new TextField(this); - inviteeInput_->setLabel(tr("User ID to invite")); - - inviteeList_ = new QListWidget; - inviteeList_->setFrameStyle(QFrame::NoFrame); - inviteeList_->setSelectionMode(QAbstractItemView::NoSelection); - inviteeList_->setAttribute(Qt::WA_MacShowFocusRect, 0); - inviteeList_->setSpacing(5); - - errorLabel_ = new QLabel(this); - errorLabel_->setAlignment(Qt::AlignCenter); - - layout->addWidget(inviteeInput_); - layout->addWidget(errorLabel_); - layout->addWidget(inviteeList_); - layout->addLayout(buttonLayout); - - connect(inviteeInput_, &TextField::returnPressed, this, &InviteUsers::addUser); - connect(confirmBtn_, &QPushButton::clicked, [this]() { - if (!inviteeInput_->text().trimmed().isEmpty()) { - addUser(); - } - - emit sendInvites(invitedUsers()); - - inviteeInput_->clear(); - inviteeList_->clear(); - errorLabel_->hide(); - - emit close(); - }); - - connect(cancelBtn_, &QPushButton::clicked, [this]() { - inviteeInput_->clear(); - inviteeList_->clear(); - errorLabel_->hide(); - - emit close(); - }); -} - -void -InviteUsers::addUser() -{ - auto user_id = inviteeInput_->text(); - - try { - namespace ids = mtx::identifiers; - auto user = ids::parse(user_id.toStdString()); - - auto item = new QListWidgetItem(inviteeList_); - auto invitee = new InviteeItem(user, this); - - item->setSizeHint(invitee->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - inviteeList_->setItemWidget(item, invitee); - - connect(invitee, &InviteeItem::removeItem, this, [this, item]() { - emit removeInvitee(item); - }); - - errorLabel_->hide(); - inviteeInput_->clear(); - } catch (std::exception &e) { - errorLabel_->setText(e.what()); - errorLabel_->show(); - } -} - -void -InviteUsers::removeInvitee(QListWidgetItem *item) -{ - int row = inviteeList_->row(item); - auto widget = inviteeList_->takeItem(row); - - inviteeList_->removeItemWidget(widget); -} - -QStringList -InviteUsers::invitedUsers() const -{ - QStringList users; - - for (int ii = 0; ii < inviteeList_->count(); ++ii) { - auto item = inviteeList_->item(ii); - auto widget = inviteeList_->itemWidget(item); - auto invitee = qobject_cast(widget); - - if (invitee) - users << invitee->userID(); - else - qDebug() << "Cast InviteeItem failed"; - } - - return users; -} - -void -InviteUsers::showEvent(QShowEvent *event) -{ - inviteeInput_->setFocus(); - - QFrame::showEvent(event); -} diff --git a/src/dialogs/InviteUsers.h b/src/dialogs/InviteUsers.h deleted file mode 100644 index e40183c1..00000000 --- a/src/dialogs/InviteUsers.h +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class QPushButton; -class QLabel; -class TextField; -class QListWidget; -class QListWidgetItem; - -namespace dialogs { - -class InviteUsers : public QFrame -{ - Q_OBJECT -public: - explicit InviteUsers(QWidget *parent = nullptr); - -protected: - void showEvent(QShowEvent *event) override; - -signals: - void sendInvites(QStringList invitees); - -private slots: - void removeInvitee(QListWidgetItem *item); - -private: - void addUser(); - QStringList invitedUsers() const; - - QPushButton *confirmBtn_; - QPushButton *cancelBtn_; - - TextField *inviteeInput_; - QLabel *errorLabel_; - - QListWidget *inviteeList_; -}; -} // dialogs diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index feb7b5f5..5730fbab 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -159,6 +159,7 @@ class TimelineModel : public QAbstractListModel Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit) Q_PROPERTY( bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) + Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 011ff61c..43b9a646 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -429,6 +429,57 @@ TimelineViewManager::openInviteUsersDialog() MainWindow::instance()->openInviteUsersDialog( [this](const QStringList &invitees) { emit inviteUsers(invitees); }); } + +void +TimelineViewManager::openLink(QString link) const +{ + QUrl url(link); + if (url.scheme() == "https" && url.host() == "matrix.to") { + // handle matrix.to links internally + QString p = url.fragment(QUrl::FullyEncoded); + if (p.startsWith("/")) + p.remove(0, 1); + + auto temp = p.split("?"); + QString query; + if (temp.size() >= 2) + query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); + + temp = temp.first().split("/"); + auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); + QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); + if (!identifier.isEmpty()) { + if (identifier.startsWith("@")) { + QByteArray uri = + "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!query.isEmpty()) + uri.append("?" + query.toUtf8()); + ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); + } else if (identifier.startsWith("#")) { + QByteArray uri = + "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!eventId.isEmpty()) + uri.append("/e/" + + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + uri.append("?" + query.toUtf8()); + ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); + } else if (identifier.startsWith("!")) { + QByteArray uri = "matrix:roomid/" + + QUrl::toPercentEncoding(identifier.remove(0, 1)); + if (!eventId.isEmpty()) + uri.append("/e/" + + QUrl::toPercentEncoding(eventId.remove(0, 1))); + if (!query.isEmpty()) + uri.append("?" + query.toUtf8()); + ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); + } + } + } else { + QDesktopServices::openUrl(url); + } +} + void TimelineViewManager::openLeaveRoomDialog(QString roomid) const { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 39d0d31c..945ba2d5 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -65,7 +65,6 @@ public: Q_INVOKABLE QString userStatus(QString id) const; Q_INVOKABLE void focusMessageInput(); - Q_INVOKABLE void openInviteUsersDialog(); Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); @@ -80,7 +79,9 @@ signals: void replyingEventChanged(QString replyingEvent); void replyClosed(); void newDeviceVerificationRequest(DeviceVerificationFlow *flow); - void inviteUsers(QStringList users); + void inviteUsers(QString roomId, QStringList users); + void showRoomList(); + void narrowViewChanged(); void focusChanged(); void focusInput(); void openImageOverlayInternalCb(QString eventId, QImage img); -- cgit 1.5.1 From e1acf5d324615e8c61c469883a6a42933c8f76bc Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 10 Jun 2021 20:13:12 -0400 Subject: make lint --- CMakeLists.txt | 2 + resources/qml/InviteDialog.qml | 78 +++++++++++++++++++++++------------- resources/qml/RoomMembers.qml | 9 +++++ resources/qml/Root.qml | 38 ++++++++++++++++++ resources/qml/TimelineView.qml | 1 - resources/qml/TopBar.qml | 14 +------ resources/qml/types/Invitee.qml | 5 --- resources/res.qrc | 1 - src/ChatPage.cpp | 49 +++++++++++----------- src/InviteesModel.cpp | 77 +++++++++++++++++++++++++++++++++++ src/InviteesModel.h | 56 ++++++++++++++++++++++++++ src/MemberList.cpp | 10 +++-- src/timeline/TimelineModel.cpp | 10 +++++ src/timeline/TimelineModel.h | 3 ++ src/timeline/TimelineViewManager.cpp | 70 +++++++------------------------- 15 files changed, 290 insertions(+), 133 deletions(-) delete mode 100644 resources/qml/types/Invitee.qml create mode 100644 src/InviteesModel.cpp create mode 100644 src/InviteesModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 56592950..f77d9978 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,6 +344,7 @@ set(SRC_FILES src/CompletionProxyModel.cpp src/DeviceVerificationFlow.cpp src/EventAccessors.cpp + src/InviteesModel.cpp src/Logging.cpp src/LoginPage.cpp src/MainWindow.cpp @@ -550,6 +551,7 @@ qt5_wrap_cpp(MOC_HEADERS src/Clipboard.h src/CompletionProxyModel.h src/DeviceVerificationFlow.h + src/InviteesModel.h src/LoginPage.h src/MainWindow.h src/MemberList.h diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 5d3a8f1e..d5cc4c6d 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -2,50 +2,28 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import im.nheko 1.0 -import "./types" ApplicationWindow { id: inviteDialogRoot property string roomId property string roomName - property list invitees + property InviteesModel invitees function addInvite() { if (inviteeEntry.text.match("@.+?:.{3,}")) { - invitees.push(inviteeComponent.createObject( - inviteDialogRoot, { - "invitee": inviteeEntry.text - })); + invitees.addUser(inviteeEntry.text); inviteeEntry.clear(); } } - function accept() { - if (inviteeEntry.text !== "") - addInvite(); - - var inviteeStringList = ["temp"]; // the "temp" element exists to declare this as a string array - for (var i = 0; i < invitees.length; ++i) - inviteeStringList.push(invitees[i].invitee); - inviteeStringList.shift(); // remove the first item - - TimelineManager.inviteUsers(inviteDialogRoot.roomId, inviteeStringList); - } - title: qsTr("Invite users to ") + roomName x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 width: 340 - Component { - id: inviteeComponent - - Invitee {} - } - // TODO: make this work in the TextField Shortcut { sequence: "Ctrl+Enter" @@ -74,7 +52,7 @@ ApplicationWindow { } Button { - text: qsTr("Invite") + text: qsTr("Add") onClicked: if (inviteeEntry.text !== "") addInvite() } } @@ -85,9 +63,53 @@ ApplicationWindow { Layout.fillWidth: true Layout.fillHeight: true model: invitees - delegate: Label { - text: model.invitee + + delegate: RowLayout { + spacing: 10 + + Avatar { + width: avatarSize + height: avatarSize + userid: model.mxid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.displayName + onClicked: TimelineManager.timeline.openUserProfile(model.mxid) + } + + ColumnLayout { + spacing: 5 + + Label { + text: model.displayName + color: TimelineManager.userColor(model ? model.mxid : "", colors.window) + font.pointSize: 12 + } + + Label { + text: model.mxid + color: colors.buttonText + font.pointSize: 10 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } } +// delegate: RowLayout { +// spacing: 10 + +// Avatar { +// url: model.avatarUrl +// width: 20 +// height: width +// } + +// Label { +// text: model.displayName + " (" + model.mxid + ")" +// } +// } } } @@ -98,7 +120,7 @@ ApplicationWindow { text: qsTr("Invite") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole onClicked: { - inviteDialogRoot.accept(); + invitees.accept(); inviteDialogRoot.close(); } } diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 4406c1b0..d31fe319 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -44,6 +44,15 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter } + ImageButton { + Layout.alignment: Qt.AlignHCenter + image: ":/icons/icons/ui/add-square-button.png" + hoverEnabled: true + ToolTip.visible: hovered + ToolTip.text: qsTr("Invite more people") + onClicked: TimelineManager.timeline.openInviteUsersDialog() + } + ScrollView { clip: false palette: colors diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 5316e20d..ecd0bdb7 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -89,6 +89,12 @@ Page { } } + Component { + id: inviteDialog + + InviteDialog { + } + } Connections { target: TimelineManager @@ -116,6 +122,38 @@ Page { } } + Connections { + target: TimelineManager.timeline + onOpenRoomMembersDialog: { + var membersDialog = roomMembersComponent.createObject(timelineRoot, { + "members": members, + "roomName": TimelineManager.timeline.roomName + }); + membersDialog.show(); + } + } + + Connections { + target: TimelineManager.timeline + onOpenRoomSettingsDialog: { + var roomSettings = roomSettingsComponent.createObject(timelineRoot, { + "roomSettings": settings + }); + roomSettings.show(); + } + } + + Connections { + target: TimelineManager.timeline + onOpenInviteUsersDialog: { + var dialog = inviteDialog.createObject(timelineRoot, { + "roomId": TimelineManager.timeline.roomId, + "roomName": TimelineManager.timeline.roomName + }); + dialog.show(); + } + } + ChatPage { anchors.fill: parent } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 148a5817..d515b9b4 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -157,7 +157,6 @@ Item { Layout.alignment: Qt.AlignHCenter enabled: false } - MatrixText { text: parent.roomName font.pixelSize: 24 diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 72dbe604..6cf747c5 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -21,12 +21,6 @@ Rectangle { z: 3 color: Nheko.colors.window - Component { - id: inviteDialog - - InviteDialog {} - } - TapHandler { onSingleTapped: { if (room) @@ -117,13 +111,7 @@ Rectangle { Platform.MenuItem { visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") - onTriggered: { - var dialog = inviteDialog.createObject(topBar, { - "roomId": room.roomId, - "roomName": room.roomName - }); - dialog.show(); - } + onTriggered: TimelineManager.timeline.openInviteUsers() } Platform.MenuItem { diff --git a/resources/qml/types/Invitee.qml b/resources/qml/types/Invitee.qml deleted file mode 100644 index fbc0b781..00000000 --- a/resources/qml/types/Invitee.qml +++ /dev/null @@ -1,5 +0,0 @@ -import QtQuick 2.12 - -Item { - property string invitee -} diff --git a/resources/res.qrc b/resources/res.qrc index ad7b6665..f8c040e4 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -176,7 +176,6 @@ qml/components/FlatButton.qml qml/RoomMembers.qml qml/InviteDialog.qml - qml/types/Invitee.qml media/ring.ogg diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index f6ea4539..8b4cfeef 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -116,32 +116,31 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); - connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { - const auto room_id = currentRoom().toStdString(); - - for (int ii = 0; ii < users.size(); ++ii) { - QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { - const auto user = users.at(ii); - - http::client()->invite_user( - room_id, - user.toStdString(), - [this, user](const mtx::responses::RoomInvite &, - mtx::http::RequestErr err) { - if (err) { - emit showNotification( - tr("Failed to invite user: %1").arg(user)); - return; - } + // TODO: once this signal is moved, reenable this +// connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { +// const auto room_id = currentRoom().toStdString(); + +// for (int ii = 0; ii < users.size(); ++ii) { +// QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { +// const auto user = users.at(ii); + +// http::client()->invite_user( +// room_id, +// user.toStdString(), +// [this, user](const mtx::responses::RoomInvite &, +// mtx::http::RequestErr err) { +// if (err) { +// emit showNotification( +// tr("Failed to invite user: %1").arg(user)); +// return; +// } + +// emit showNotification(tr("Invited user: %1").arg(user)); +// }); +// }); +// } +// }); - emit showNotification(tr("Invited user: %1").arg(user)); - }); - }); - } - }); - - connect( - view_manager_, &TimelineViewManager::showRoomList, splitter, &Splitter::showFullRoomList); connect( view_manager_, &TimelineViewManager::inviteUsers, diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp new file mode 100644 index 00000000..849c5281 --- /dev/null +++ b/src/InviteesModel.cpp @@ -0,0 +1,77 @@ +#include "InviteesModel.h" + +#include "Cache.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "mtx/responses/profile.hpp" + +InviteesModel::InviteesModel(QObject *parent) + : QAbstractListModel{parent} +{} + +void +InviteesModel::addUser(QString mxid) +{ + beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); + + auto invitee = new Invitee{mxid, this}; + connect(invitee, &Invitee::userInfoLoaded, this, [this]() { + emit dataChanged(QModelIndex{}, QModelIndex{}); + }); + + invitees_.push_back(invitee); + + endInsertRows(); +} + +QHash +InviteesModel::roleNames() const +{ + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; +} + +QVariant +InviteesModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return invitees_[index.row()]->mxid_; + case DisplayName: + return invitees_[index.row()]->displayName_; + case AvatarUrl: + return invitees_[index.row()]->avatarUrl_; + default: + return {}; + } +} + +QStringList +InviteesModel::mxids() +{ + QStringList mxidList; + for (int i = 0; i < invitees_.length(); ++i) + mxidList.push_back(invitees_[i]->mxid_); + return mxidList; +} + +Invitee::Invitee(const QString &mxid, QObject *parent) + : QObject{parent} + , mxid_{mxid} +{ + http::client()->get_profile( + mxid_.toStdString(), + [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to retrieve own profile info"); + return; + } + + displayName_ = QString::fromStdString(res.display_name); + avatarUrl_ = QString::fromStdString(res.avatar_url); + + emit userInfoLoaded(); + }); +} diff --git a/src/InviteesModel.h b/src/InviteesModel.h new file mode 100644 index 00000000..4bcc4e9d --- /dev/null +++ b/src/InviteesModel.h @@ -0,0 +1,56 @@ +#ifndef INVITEESMODEL_H +#define INVITEESMODEL_H + +#include +#include + +class Invitee : public QObject +{ + Q_OBJECT + +public: + Invitee(const QString &mxid, QObject *parent = nullptr); + +signals: + void userInfoLoaded(); + +private: + const QString mxid_; + QString displayName_; + QString avatarUrl_; + + friend class InviteesModel; +}; + +class InviteesModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + }; + + InviteesModel(QObject *parent = nullptr); + + Q_INVOKABLE void addUser(QString mxid); + + QHash roleNames() const override; + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return (int)invitees_.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QStringList mxids(); + +signals: + void accept(); + +private: + QVector invitees_; +}; + +#endif // INVITEESMODEL_H diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 62488277..2a9c3fbc 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -43,7 +43,8 @@ MemberList::MemberList(const QString &room_id, QWidget *parent) void MemberList::addUsers(const std::vector &members) { - beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); + beginInsertRows( + QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1); for (const auto &member : members) m_memberList.push_back( @@ -78,10 +79,11 @@ MemberList::data(const QModelIndex &index, int role) const } } -bool MemberList::canFetchMore(const QModelIndex &) const +bool +MemberList::canFetchMore(const QModelIndex &) const { - const size_t numMembers = rowCount(); - return (numMembers > 1 && numMembers < info_.member_count); + const size_t numMembers = rowCount(); + return (numMembers > 1 && numMembers < info_.member_count); } void diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 48d69493..2127801c 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1076,6 +1076,16 @@ TimelineModel::openRoomSettings(QString room_id) openRoomSettingsDialog(settings); } +void +TimelineModel::openInviteUsers(QString room_id) +{ + InviteesModel *model = new InviteesModel{this}; + connect(model, &InviteesModel::accept, this, [this, model, room_id]() { + manager_->inviteUsers(room_id, model->mxids()); + }); + openInviteUsersDialog(model); +} + void TimelineModel::replyAction(QString id) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 5730fbab..e5189e61 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -17,6 +17,7 @@ #include "CacheStructs.h" #include "EventStore.h" #include "InputBar.h" +#include "InviteesModel.h" #include "MemberList.h" #include "Permissions.h" #include "ui/RoomSettings.h" @@ -239,6 +240,7 @@ public: Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void openRoomMembers(); Q_INVOKABLE void openRoomSettings(QString room_id = QString()); + Q_INVOKABLE void openInviteUsers(QString room_id = QString()); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; @@ -357,6 +359,7 @@ signals: void openRoomMembersDialog(MemberList *members); void openRoomSettingsDialog(RoomSettings *settings); + void openInviteUsersDialog(InviteesModel *invitees); void newMessageToSend(mtx::events::collections::TimelineEvents event); void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 43b9a646..08b88efd 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -20,6 +20,7 @@ #include "DeviceVerificationFlow.h" #include "EventAccessors.h" #include "ImagePackModel.h" +#include "InviteesModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" @@ -184,6 +185,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Room Settings needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "InviteesModel", + "InviteesModel needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( @@ -423,62 +430,13 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -void -TimelineViewManager::openInviteUsersDialog() -{ - MainWindow::instance()->openInviteUsersDialog( - [this](const QStringList &invitees) { emit inviteUsers(invitees); }); -} - -void -TimelineViewManager::openLink(QString link) const -{ - QUrl url(link); - if (url.scheme() == "https" && url.host() == "matrix.to") { - // handle matrix.to links internally - QString p = url.fragment(QUrl::FullyEncoded); - if (p.startsWith("/")) - p.remove(0, 1); - - auto temp = p.split("?"); - QString query; - if (temp.size() >= 2) - query = QUrl::fromPercentEncoding(temp.takeAt(1).toUtf8()); - - temp = temp.first().split("/"); - auto identifier = QUrl::fromPercentEncoding(temp.takeFirst().toUtf8()); - QString eventId = QUrl::fromPercentEncoding(temp.join('/').toUtf8()); - if (!identifier.isEmpty()) { - if (identifier.startsWith("@")) { - QByteArray uri = - "matrix:u/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } else if (identifier.startsWith("#")) { - QByteArray uri = - "matrix:r/" + QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!eventId.isEmpty()) - uri.append("/e/" + - QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } else if (identifier.startsWith("!")) { - QByteArray uri = "matrix:roomid/" + - QUrl::toPercentEncoding(identifier.remove(0, 1)); - if (!eventId.isEmpty()) - uri.append("/e/" + - QUrl::toPercentEncoding(eventId.remove(0, 1))); - if (!query.isEmpty()) - uri.append("?" + query.toUtf8()); - ChatPage::instance()->handleMatrixUri(QUrl::fromEncoded(uri)); - } - } - } else { - QDesktopServices::openUrl(url); - } -} +//void +//TimelineViewManager::openInviteUsersDialog() +//{ + // TODO: move this somewhere where it will actually work (probably Rooms) +// MainWindow::instance()->openInviteUsersDialog( +// [this](const QStringList &invitees) { emit inviteUsers(invitees); }); +//} void TimelineViewManager::openLeaveRoomDialog(QString roomid) const -- cgit 1.5.1 From a76fc7d20058fa10e1345e0de0a1431a7161cd57 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 18:53:32 -0400 Subject: Add a fancy loading spinner to the member list --- resources/qml/RoomMembers.qml | 7 +++++++ src/MemberList.cpp | 15 ++++++++++++--- src/MemberList.h | 4 ++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index d31fe319..8060a6cc 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -109,6 +109,13 @@ ApplicationWindow { } } } + + footer: BusyIndicator { + // This is not a wonderful solution, but it is the best way to calculate whether + // all users are loaded while keeping canFetchMore() const + running: members.numUsersLoaded < members.memberCount + anchors.centerIn: parent + } } } } diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 2a9c3fbc..da4412d2 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -34,7 +34,10 @@ MemberList::MemberList(const QString &room_id, QWidget *parent) } try { - addUsers(cache::getMembers(room_id_.toStdString())); + auto members = cache::getMembers(room_id_.toStdString()); + addUsers(members); + numUsersLoaded_ = members.size(); + emit numUsersLoadedChanged(); } catch (const lmdb::error &e) { nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); } @@ -83,11 +86,17 @@ bool MemberList::canFetchMore(const QModelIndex &) const { const size_t numMembers = rowCount(); - return (numMembers > 1 && numMembers < info_.member_count); + if (numMembers > 1 && numMembers < info_.member_count) + return true; + else + return false; } void MemberList::fetchMore(const QModelIndex &) { - addUsers(cache::getMembers(room_id_.toStdString(), rowCount())); + auto members = cache::getMembers(room_id_.toStdString(), rowCount()); + addUsers(members); + numUsersLoaded_ = members.size(); + emit numUsersLoadedChanged(); } diff --git a/src/MemberList.h b/src/MemberList.h index dbe69f4b..afc1a6e5 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -15,6 +15,7 @@ class MemberList : public QAbstractListModel Q_PROPERTY(size_t memberCount READ memberCount NOTIFY memberCountChanged) Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) + Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) public: enum Roles @@ -37,12 +38,14 @@ public: size_t memberCount() const { return info_.member_count; } QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } QString roomId() const { return room_id_; } + int numUsersLoaded() const { return numUsersLoaded_; } signals: void roomNameChanged(); void memberCountChanged(); void avatarUrlChanged(); void roomIdChanged(); + void numUsersLoadedChanged(); public slots: void addUsers(const std::vector &users); @@ -55,4 +58,5 @@ private: QVector> m_memberList; QString room_id_; RoomInfo info_; + int numUsersLoaded_; }; -- cgit 1.5.1 From f0c88fc47435bda93a55271eb830bf80dc88443f Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 19:18:04 -0400 Subject: Get member info loading working --- src/InviteesModel.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 849c5281..0081c7df 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -16,12 +16,10 @@ InviteesModel::addUser(QString mxid) auto invitee = new Invitee{mxid, this}; connect(invitee, &Invitee::userInfoLoaded, this, [this]() { - emit dataChanged(QModelIndex{}, QModelIndex{}); + endInsertRows(); }); invitees_.push_back(invitee); - - endInsertRows(); } QHash -- cgit 1.5.1 From a176de5f11f11bb3dad6cb8ea71a3b5edd6f27f4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 20:46:57 -0400 Subject: Make sure to use the default room id if none is specified --- resources/qml/InviteDialog.qml | 3 ++- src/timeline/TimelineModel.cpp | 6 +++--- src/timeline/TimelineModel.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index d5cc4c6d..278f772f 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -43,10 +43,11 @@ ApplicationWindow { RowLayout { spacing: 10 - TextField { + MatrixTextField { id: inviteeEntry placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") + backgroundColor: colors.window Layout.fillWidth: true onAccepted: if (text !== "") addInvite() } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 2127801c..ebbca6f4 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1077,11 +1077,11 @@ TimelineModel::openRoomSettings(QString room_id) } void -TimelineModel::openInviteUsers(QString room_id) +TimelineModel::openInviteUsers(QString roomId) { InviteesModel *model = new InviteesModel{this}; - connect(model, &InviteesModel::accept, this, [this, model, room_id]() { - manager_->inviteUsers(room_id, model->mxids()); + connect(model, &InviteesModel::accept, this, [this, model, roomId]() { + manager_->inviteUsers(roomId == QString() ? room_id_ : roomId, model->mxids()); }); openInviteUsersDialog(model); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index e5189e61..b5144308 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -240,7 +240,7 @@ public: Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void openRoomMembers(); Q_INVOKABLE void openRoomSettings(QString room_id = QString()); - Q_INVOKABLE void openInviteUsers(QString room_id = QString()); + Q_INVOKABLE void openInviteUsers(QString roomId = QString()); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; -- cgit 1.5.1 From 3c999ade95785987d2213036067110bc1bb4fcb1 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 20:47:43 -0400 Subject: Focus the input bar automatically --- resources/qml/InviteDialog.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 278f772f..002a35c2 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -50,6 +50,7 @@ ApplicationWindow { backgroundColor: colors.window Layout.fillWidth: true onAccepted: if (text !== "") addInvite() + Component.onCompleted: forceActiveFocus() } Button { -- cgit 1.5.1 From 4746fcd16f458c9b88e5e50074c33254da7e1141 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 20:48:20 -0400 Subject: Add fancy label if you enter a bad mxid --- resources/qml/InviteDialog.qml | 73 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 002a35c2..9ace3246 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -16,6 +16,10 @@ ApplicationWindow { invitees.addUser(inviteeEntry.text); inviteeEntry.clear(); } + else + { + warningLabel.show() + } } title: qsTr("Invite users to ") + roomName @@ -51,6 +55,11 @@ ApplicationWindow { Layout.fillWidth: true onAccepted: if (text !== "") addInvite() Component.onCompleted: forceActiveFocus() + + Shortcut { + sequence: "Ctrl+Enter" + onActivated: inviteDialogRoot.accept() + } } Button { @@ -59,6 +68,70 @@ ApplicationWindow { } } + Label { + id: warningLabel + + function show() { + state = "shown"; + warningLabelTimer.start(); + } + + text: qsTr("Please enter a valid username (e.g. @joe:matrix.org).") + color: "red" + visible: false + opacity: 0 + state: "hidden" + + states: [ + State { + name: "shown" + PropertyChanges { + target: warningLabel + opacity: 1 + visible: true + } + }, + State { + name: "hidden" + PropertyChanges { + target: warningLabel + opacity: 0 + visible: false + } + } + ] + + transitions: [ + Transition { + from: "shown" + to: "hidden" + reversible: true + + SequentialAnimation { + NumberAnimation { + target: warningLabel + property: "opacity" + duration: 500 + } + + PropertyAction { + target: warningLabel + property: "visible" + } + } + } + ] + + Timer { + id: warningLabelTimer + + interval: 2000 + repeat: false + running: false + onTriggered: warningLabel.state = "hidden" + } + } + ListView { id: inviteesList -- cgit 1.5.1 From c566a6254138037d8916bedf9660a66f2d65187f Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 20:48:28 -0400 Subject: Clean up code --- resources/qml/InviteDialog.qml | 13 ------------- src/InviteesModel.cpp | 4 +--- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 9ace3246..d8e176e6 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -172,19 +172,6 @@ ApplicationWindow { } } } -// delegate: RowLayout { -// spacing: 10 - -// Avatar { -// url: model.avatarUrl -// width: 20 -// height: width -// } - -// Label { -// text: model.displayName + " (" + model.mxid + ")" -// } -// } } } diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 0081c7df..1da7baf4 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -15,9 +15,7 @@ InviteesModel::addUser(QString mxid) beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); auto invitee = new Invitee{mxid, this}; - connect(invitee, &Invitee::userInfoLoaded, this, [this]() { - endInsertRows(); - }); + connect(invitee, &Invitee::userInfoLoaded, this, [this]() { endInsertRows(); }); invitees_.push_back(invitee); } -- cgit 1.5.1 From 03acced6d6c5b95c2272f8d3036a6295d048a4f5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 21:04:35 -0400 Subject: Add close on Escape shortcut --- resources/qml/InviteDialog.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index d8e176e6..3470a7f1 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -34,6 +34,11 @@ ApplicationWindow { onActivated: inviteDialogRoot.accept() } + Shortcut { + sequence: StandardKey.Cancel + onActivated: inviteDialogRoot.close() + } + ColumnLayout { anchors.fill: parent anchors.margins: 10 -- cgit 1.5.1 From 908629bec0ff490b42aa1f0b1c8fade160606e96 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 11 Jun 2021 21:05:18 -0400 Subject: Fix item that accept() is called on --- resources/qml/InviteDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 3470a7f1..8ba0f262 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -31,7 +31,7 @@ ApplicationWindow { // TODO: make this work in the TextField Shortcut { sequence: "Ctrl+Enter" - onActivated: inviteDialogRoot.accept() + onActivated: invitees.accept() } Shortcut { @@ -63,7 +63,7 @@ ApplicationWindow { Shortcut { sequence: "Ctrl+Enter" - onActivated: inviteDialogRoot.accept() + onActivated: invitees.accept() } } -- cgit 1.5.1 From 59a2630be74898c5d8c13dbf0489e5caa94063fd Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 12 Jun 2021 12:00:59 -0400 Subject: Simplify room details access This removes the redundant room name property --- resources/qml/InviteDialog.qml | 1 - resources/qml/RoomMembers.qml | 5 ++--- resources/qml/TopBar.qml | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 8ba0f262..2932e398 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -56,7 +56,6 @@ ApplicationWindow { id: inviteeEntry placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") - backgroundColor: colors.window Layout.fillWidth: true onAccepted: if (text !== "") addInvite() Component.onCompleted: forceActiveFocus() diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 8060a6cc..8addd704 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -7,10 +7,9 @@ import im.nheko 1.0 ApplicationWindow { id: roomMembersRoot - property string roomName: Rooms.currentRoom.roomName property MemberList members - title: qsTr("Members of ") + roomName + title: qsTr("Members of ") + members.roomName x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 650 @@ -40,7 +39,7 @@ ApplicationWindow { Label { font.pixelSize: 24 - text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + roomName + text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName Layout.alignment: Qt.AlignHCenter } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 6cf747c5..aa5c5d18 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -111,12 +111,12 @@ Rectangle { Platform.MenuItem { visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") - onTriggered: TimelineManager.timeline.openInviteUsers() + onTriggered: Rooms.currentRoom.openInviteUsers() } Platform.MenuItem { text: qsTr("Members") - onTriggered: Rooms.currentRoom.openRoomMembers(room.roomId()) + onTriggered: Rooms.currentRoom.openRoomMembers() } Platform.MenuItem { -- cgit 1.5.1 From 60b3c34d78120d10114fc14600b2b142bbc80362 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 12 Jun 2021 13:17:31 -0400 Subject: Permissions only needs a roomid to function --- src/timeline/Permissions.cpp | 6 +++--- src/timeline/Permissions.h | 4 ++-- src/timeline/TimelineModel.cpp | 1 + src/timeline/TimelineModel.h | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp index 1eaab468..e4957045 100644 --- a/src/timeline/Permissions.cpp +++ b/src/timeline/Permissions.cpp @@ -8,9 +8,9 @@ #include "MatrixClient.h" #include "TimelineModel.h" -Permissions::Permissions(TimelineModel *parent) +Permissions::Permissions(QString roomId, QObject *parent) : QObject(parent) - , room(parent) + , roomId_(roomId) { invalidate(); } @@ -19,7 +19,7 @@ void Permissions::invalidate() { pl = cache::client() - ->getStateEvent(room->roomId().toStdString()) + ->getStateEvent(roomId_.toStdString()) .value_or(mtx::events::StateEvent{}) .content; } diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h index f7e6f389..7aab1ddb 100644 --- a/src/timeline/Permissions.h +++ b/src/timeline/Permissions.h @@ -15,7 +15,7 @@ class Permissions : public QObject Q_OBJECT public: - Permissions(TimelineModel *parent); + Permissions(QString roomId, QObject *parent = nullptr); Q_INVOKABLE bool canInvite(); Q_INVOKABLE bool canBan(); @@ -28,6 +28,6 @@ public: void invalidate(); private: - TimelineModel *room; + QString roomId_; mtx::events::state::PowerLevels pl; }; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index ebbca6f4..516a499b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -318,6 +318,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj , events(room_id.toStdString(), this) , room_id_(room_id) , manager_(manager) + , permissions_{room_id} { lastMessage_.timestamp = 0; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index b5144308..ebf24bec 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -396,7 +396,7 @@ private: TimelineViewManager *manager_; InputBar input_{this}; - Permissions permissions_{this}; + Permissions permissions_; QTimer showEventTimer{this}; QString eventIdToShow; -- cgit 1.5.1 From db8af24bea5b46b76c0afd90813a4a29e13eea28 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 12 Jun 2021 22:00:14 -0400 Subject: Don't emit signal in constructor --- src/MemberList.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MemberList.cpp b/src/MemberList.cpp index da4412d2..7f4dfa99 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -37,7 +37,6 @@ MemberList::MemberList(const QString &room_id, QWidget *parent) auto members = cache::getMembers(room_id_.toStdString()); addUsers(members); numUsersLoaded_ = members.size(); - emit numUsersLoadedChanged(); } catch (const lmdb::error &e) { nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what()); } -- cgit 1.5.1 From 182de32380d192a6dcf71b656e5e5cd3bab30a6e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 15 Jun 2021 12:32:00 -0400 Subject: Use standard buttons for OK button --- resources/qml/RoomSettings.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index c852b837..a27be13e 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -280,10 +280,10 @@ ApplicationWindow { } - Button { - Layout.alignment: Qt.AlignRight - text: qsTr("OK") - onClicked: close() + DialogButtonBox { + Layout.fillWidth: true + standardButtons: DialogButtonBox.Ok + onAccepted: close() } } -- cgit 1.5.1 From d2d5229ede5124ba6cf9e85790dcd564faad00db Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 13:31:38 -0400 Subject: make lint --- resources/qml/InviteDialog.qml | 44 ++++++++++++++++++++++++-------- resources/qml/RoomMembers.qml | 4 +++ resources/qml/TimelineView.qml | 1 + src/ChatPage.cpp | 49 +++++++++++++++++++----------------- src/InviteesModel.cpp | 4 +++ src/InviteesModel.h | 4 +++ src/timeline/TimelineViewManager.cpp | 6 ++--- 7 files changed, 75 insertions(+), 37 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 2932e398..ae74d3da 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 @@ -11,14 +15,11 @@ ApplicationWindow { property InviteesModel invitees function addInvite() { - if (inviteeEntry.text.match("@.+?:.{3,}")) - { + if (inviteeEntry.text.match("@.+?:.{3,}")) { invitees.addUser(inviteeEntry.text); inviteeEntry.clear(); - } - else - { - warningLabel.show() + } else { + warningLabel.show(); } } @@ -57,19 +58,29 @@ ApplicationWindow { placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") Layout.fillWidth: true - onAccepted: if (text !== "") addInvite() + onAccepted: { + if (text !== "") { + addInvite(); + } + } Component.onCompleted: forceActiveFocus() Shortcut { sequence: "Ctrl+Enter" onActivated: invitees.accept() } + } Button { text: qsTr("Add") - onClicked: if (inviteeEntry.text !== "") addInvite() + onClicked: { + if (inviteeEntry.text !== "") { + addInvite(); + } + } } + } Label { @@ -85,26 +96,28 @@ ApplicationWindow { visible: false opacity: 0 state: "hidden" - states: [ State { name: "shown" + PropertyChanges { target: warningLabel opacity: 1 visible: true } + }, State { name: "hidden" + PropertyChanges { target: warningLabel opacity: 0 visible: false } + } ] - transitions: [ Transition { from: "shown" @@ -122,7 +135,9 @@ ApplicationWindow { target: warningLabel property: "visible" } + } + } ] @@ -134,6 +149,7 @@ ApplicationWindow { running: false onTriggered: warningLabel.state = "hidden" } + } ListView { @@ -174,9 +190,13 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true } + } + } + } + } footer: DialogButtonBox { @@ -194,7 +214,9 @@ ApplicationWindow { Button { text: qsTr("Cancel") DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole - onClicked: inviteDialogRoot.close(); + onClicked: inviteDialogRoot.close() } + } + } diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 8addd704..44b917b1 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index d515b9b4..148a5817 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -157,6 +157,7 @@ Item { Layout.alignment: Qt.AlignHCenter enabled: false } + MatrixText { text: parent.roomName font.pixelSize: 24 diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 8b4cfeef..70fd32fd 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -117,29 +117,32 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); // TODO: once this signal is moved, reenable this -// connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) { -// const auto room_id = currentRoom().toStdString(); - -// for (int ii = 0; ii < users.size(); ++ii) { -// QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { -// const auto user = users.at(ii); - -// http::client()->invite_user( -// room_id, -// user.toStdString(), -// [this, user](const mtx::responses::RoomInvite &, -// mtx::http::RequestErr err) { -// if (err) { -// emit showNotification( -// tr("Failed to invite user: %1").arg(user)); -// return; -// } - -// emit showNotification(tr("Invited user: %1").arg(user)); -// }); -// }); -// } -// }); + // connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList + // users) { + // const auto room_id = currentRoom().toStdString(); + + // for (int ii = 0; ii < users.size(); ++ii) { + // QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { + // const auto user = users.at(ii); + + // http::client()->invite_user( + // room_id, + // user.toStdString(), + // [this, user](const mtx::responses::RoomInvite &, + // mtx::http::RequestErr err) { + // if (err) { + // emit showNotification( + // tr("Failed to invite user: + // %1").arg(user)); + // return; + // } + + // emit showNotification(tr("Invited user: + // %1").arg(user)); + // }); + // }); + // } + // }); connect( view_manager_, diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 1da7baf4..59054690 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #include "InviteesModel.h" #include "Cache.h" diff --git a/src/InviteesModel.h b/src/InviteesModel.h index 4bcc4e9d..ac9208a0 100644 --- a/src/InviteesModel.h +++ b/src/InviteesModel.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + #ifndef INVITEESMODEL_H #define INVITEESMODEL_H diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 08b88efd..8daa2124 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -430,10 +430,10 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -//void -//TimelineViewManager::openInviteUsersDialog() +// void +// TimelineViewManager::openInviteUsersDialog() //{ - // TODO: move this somewhere where it will actually work (probably Rooms) +// TODO: move this somewhere where it will actually work (probably Rooms) // MainWindow::instance()->openInviteUsersDialog( // [this](const QStringList &invitees) { emit inviteUsers(invitees); }); //} -- cgit 1.5.1 From 913d0fd13985712f48ee50dcdd46618606364f91 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 15:52:10 -0400 Subject: Make macOS CI happy (again) --- src/MemberList.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MemberList.h b/src/MemberList.h index afc1a6e5..80ab834c 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -51,8 +51,8 @@ public slots: void addUsers(const std::vector &users); protected: - bool canFetchMore(const QModelIndex &) const; - void fetchMore(const QModelIndex &); + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; private: QVector> m_memberList; -- cgit 1.5.1 From 4ddcff2300ba37c88d8170f063ba1f0ae546efc8 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:16:02 -0400 Subject: Fix borked property stuff --- resources/qml/RoomList.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index a1ce8d7e..7c03673f 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -133,7 +133,7 @@ Page { states: [ State { name: "highlight" - when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId) + when: hovered.hovered && !((Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId) PropertyChanges { target: roomItem @@ -147,7 +147,7 @@ Page { }, State { name: "selected" - when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId()) || Rooms.currentRoomPreview.roomid == roomId + when: (Rooms.currentRoom && roomId == Rooms.currentRoom.roomId) || Rooms.currentRoomPreview.roomid == roomId PropertyChanges { target: roomItem -- cgit 1.5.1 From 5d9556722f409c9202c0b506dd789b7d2d722b01 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:16:18 -0400 Subject: Fix up components --- resources/qml/Root.qml | 49 ++++++++++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index ecd0bdb7..3825fb89 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -47,6 +47,13 @@ Page { } + Component { + id: roomMembersComponent + + RoomMembers { + } + } + Component { id: mobileCallInviteDialog @@ -63,6 +70,20 @@ Page { } + Component { + id: deviceVerificationDialog + + DeviceVerification { + } + + } + Component { + id: inviteDialog + + InviteDialog { + } + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -82,20 +103,6 @@ Page { onActivated: Rooms.previousRoom() } - Component { - id: deviceVerificationDialog - - DeviceVerification { - } - - } - Component { - id: inviteDialog - - InviteDialog { - } - } - Connections { target: TimelineManager onNewDeviceVerificationRequest: { @@ -123,18 +130,18 @@ Page { } Connections { - target: TimelineManager.timeline + target: Rooms.currentRoom onOpenRoomMembersDialog: { var membersDialog = roomMembersComponent.createObject(timelineRoot, { "members": members, - "roomName": TimelineManager.timeline.roomName + "roomName": Rooms.currentRoom.roomName }); membersDialog.show(); } } Connections { - target: TimelineManager.timeline + target: Rooms.currentRoom onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { "roomSettings": settings @@ -144,12 +151,12 @@ Page { } Connections { - target: TimelineManager.timeline + target: Rooms.currentRoom onOpenInviteUsersDialog: { var dialog = inviteDialog.createObject(timelineRoot, { - "roomId": TimelineManager.timeline.roomId, - "roomName": TimelineManager.timeline.roomName - }); + "roomId": Rooms.currentRoom.roomId, + "roomName": Rooms.currentRoom.roomName + }); dialog.show(); } } -- cgit 1.5.1 From 02326fce70372e2dc8753ba51286b24f93882559 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:17:05 -0400 Subject: Fix background color on text input --- resources/qml/InviteDialog.qml | 1 + resources/qml/MatrixTextField.qml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index ae74d3da..e171808e 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -56,6 +56,7 @@ ApplicationWindow { MatrixTextField { id: inviteeEntry + backgroundColor: Nheko.colors.window placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") Layout.fillWidth: true onAccepted: { diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml index 3c660bac..80732b27 100644 --- a/resources/qml/MatrixTextField.qml +++ b/resources/qml/MatrixTextField.qml @@ -10,6 +10,8 @@ import im.nheko 1.0 TextField { id: input + property alias backgroundColor: backgroundRect.color + palette: Nheko.colors color: Nheko.colors.text @@ -62,6 +64,8 @@ TextField { } background: Rectangle { + id: backgroundRect + color: Nheko.colors.base } -- cgit 1.5.1 From cb8d1401234b2a0f94a2e778100e0b2f757c45a2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:48:06 -0400 Subject: Fix properties --- resources/qml/RoomMembers.qml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 44b917b1..9b920f2b 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -7,6 +7,7 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Window 2.12 import im.nheko 1.0 +import "./ui" ApplicationWindow { id: roomMembersRoot @@ -38,7 +39,7 @@ ApplicationWindow { displayName: members.roomName Layout.alignment: Qt.AlignHCenter url: members.avatarUrl.replace("mxc://", "image://MxcImage/") - onClicked: TimelineManager.timeline.openRoomSettings(members.roomId) + onClicked: Rooms.currentRoom.openRoomSettings(members.roomId) } Label { @@ -53,12 +54,12 @@ ApplicationWindow { hoverEnabled: true ToolTip.visible: hovered ToolTip.text: qsTr("Invite more people") - onClicked: TimelineManager.timeline.openInviteUsersDialog() + onClicked: Rooms.currentRoom.openInviteUsersDialog() } ScrollView { clip: false - palette: colors + palette: Nheko.colors padding: 10 ScrollBar.horizontal.visible: false Layout.fillHeight: true @@ -83,12 +84,12 @@ ApplicationWindow { spacing: 10 Avatar { - width: avatarSize - height: avatarSize + width: Nheko.avatarSize + height: Nheko.avatarSize userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName - onClicked: TimelineManager.timeline.openUserProfile(model.mxid) + onClicked: Rooms.currentRoom.openUserProfile(model.mxid) } ColumnLayout { @@ -96,13 +97,13 @@ ApplicationWindow { Label { text: model.displayName - color: TimelineManager.userColor(model ? model.mxid : "", colors.window) + color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) font.pointSize: 12 } Label { text: model.mxid - color: colors.buttonText + color: Nheko.colors.buttonText font.pointSize: 10 } -- cgit 1.5.1 From 3c5b395171ae526c4564fad5182c174ff2352b65 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:48:19 -0400 Subject: Use fancy spinner (courtesy of redsky) --- resources/qml/RoomMembers.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 9b920f2b..d1d60a16 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -114,7 +114,7 @@ ApplicationWindow { } } - footer: BusyIndicator { + footer: Spinner { // This is not a wonderful solution, but it is the best way to calculate whether // all users are loaded while keeping canFetchMore() const running: members.numUsersLoaded < members.memberCount -- cgit 1.5.1 From baa9dfe110698a741eedc6209e33a4db687dffbe Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:49:34 -0400 Subject: Clean up code --- src/ChatPage.cpp | 28 ---------------------------- src/timeline/TimelineModel.cpp | 6 +++--- src/timeline/TimelineViewManager.cpp | 8 -------- 3 files changed, 3 insertions(+), 39 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 70fd32fd..6b8c1e10 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -116,34 +116,6 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) connect(this, &ChatPage::loggedOut, this, &ChatPage::logout); - // TODO: once this signal is moved, reenable this - // connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList - // users) { - // const auto room_id = currentRoom().toStdString(); - - // for (int ii = 0; ii < users.size(); ++ii) { - // QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() { - // const auto user = users.at(ii); - - // http::client()->invite_user( - // room_id, - // user.toStdString(), - // [this, user](const mtx::responses::RoomInvite &, - // mtx::http::RequestErr err) { - // if (err) { - // emit showNotification( - // tr("Failed to invite user: - // %1").arg(user)); - // return; - // } - - // emit showNotification(tr("Invited user: - // %1").arg(user)); - // }); - // }); - // } - // }); - connect( view_manager_, &TimelineViewManager::inviteUsers, diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 516a499b..7ce0e98a 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1074,7 +1074,7 @@ TimelineModel::openRoomSettings(QString room_id) { RoomSettings *settings = new RoomSettings(room_id == QString() ? roomId() : room_id, this); connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); - openRoomSettingsDialog(settings); + emit openRoomSettingsDialog(settings); } void @@ -1082,9 +1082,9 @@ TimelineModel::openInviteUsers(QString roomId) { InviteesModel *model = new InviteesModel{this}; connect(model, &InviteesModel::accept, this, [this, model, roomId]() { - manager_->inviteUsers(roomId == QString() ? room_id_ : roomId, model->mxids()); + emit manager_->inviteUsers(roomId == QString() ? room_id_ : roomId, model->mxids()); }); - openInviteUsersDialog(model); + emit openInviteUsersDialog(model); } void diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 8daa2124..64493e5b 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -430,14 +430,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) }); } -// void -// TimelineViewManager::openInviteUsersDialog() -//{ -// TODO: move this somewhere where it will actually work (probably Rooms) -// MainWindow::instance()->openInviteUsersDialog( -// [this](const QStringList &invitees) { emit inviteUsers(invitees); }); -//} - void TimelineViewManager::openLeaveRoomDialog(QString roomid) const { -- cgit 1.5.1 From 81a3faee7b7035501325a44a04eba2fbfde8a7e6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 16:56:56 -0400 Subject: Finish converting function to property --- resources/qml/Completer.qml | 2 +- resources/qml/RoomList.qml | 4 ++-- resources/qml/TimelineView.qml | 2 +- resources/qml/TopBar.qml | 2 +- resources/qml/delegates/MessageDelegate.qml | 2 +- resources/qml/voip/PlaceCall.qml | 4 ++-- resources/qml/voip/ScreenShare.qml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index 333fb11d..00fc3216 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -70,7 +70,7 @@ Popup { onCompleterNameChanged: { if (completerName) { if (completerName == "user") - completer = TimelineManager.completerFor(completerName, room.roomId()); + completer = TimelineManager.completerFor(completerName, room.roomId); else completer = TimelineManager.completerFor(completerName); completer.setSearchString(""); diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 7c03673f..9dac5830 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -33,8 +33,8 @@ Page { Connections { onActiveTimelineChanged: { - roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId()), ListView.Contain); - console.log("Test" + Rooms.currentRoom.roomId() + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId())); + roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); + console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId)); } target: TimelineManager } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 148a5817..f5979e14 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -246,7 +246,7 @@ Item { NhekoDropArea { anchors.fill: parent - roomid: room ? room.roomId() : "" + roomid: room ? room.roomId : "" } Connections { diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index aa5c5d18..48491f84 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -121,7 +121,7 @@ Rectangle { Platform.MenuItem { text: qsTr("Leave room") - onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId()) + onTriggered: TimelineManager.openLeaveRoomDialog(room.roomId) } Platform.MenuItem { diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index c64ae887..a98c2a8b 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -232,7 +232,7 @@ Item { body: formatted isOnlyEmoji: false isReply: d.isReply - formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId()) + formatted: qsTr("%1 created and configured room: %2").arg(d.userName).arg(room.roomId) } } diff --git a/resources/qml/voip/PlaceCall.qml b/resources/qml/voip/PlaceCall.qml index 5f564853..97932cc9 100644 --- a/resources/qml/voip/PlaceCall.qml +++ b/resources/qml/voip/PlaceCall.qml @@ -88,7 +88,7 @@ Popup { onClicked: { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; - CallManager.sendInvite(room.roomId(), CallType.VOICE); + CallManager.sendInvite(room.roomId, CallType.VOICE); close(); } } @@ -102,7 +102,7 @@ Popup { if (buttonLayout.validateMic()) { Settings.microphone = micCombo.currentText; Settings.camera = cameraCombo.currentText; - CallManager.sendInvite(room.roomId(), CallType.VIDEO); + CallManager.sendInvite(room.roomId, CallType.VIDEO); close(); } } diff --git a/resources/qml/voip/ScreenShare.qml b/resources/qml/voip/ScreenShare.qml index a10057b2..8cd43b1c 100644 --- a/resources/qml/voip/ScreenShare.qml +++ b/resources/qml/voip/ScreenShare.qml @@ -136,7 +136,7 @@ Popup { Settings.screenSharePiP = pipCheckBox.checked; Settings.screenShareRemoteVideo = remoteVideoCheckBox.checked; Settings.screenShareHideCursor = hideCursorCheckBox.checked; - CallManager.sendInvite(room.roomId(), CallType.SCREEN, windowCombo.currentIndex); + CallManager.sendInvite(room.roomId, CallType.SCREEN, windowCombo.currentIndex); close(); } } -- cgit 1.5.1 From 462204f3f462a5f348227379b5743405df38ff23 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 18:23:03 -0400 Subject: Fix properties --- resources/qml/InviteDialog.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index e171808e..83471658 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -164,8 +164,8 @@ ApplicationWindow { spacing: 10 Avatar { - width: avatarSize - height: avatarSize + width: Nheko.avatarsize + height: Nheko.avatarsize userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName @@ -177,13 +177,13 @@ ApplicationWindow { Label { text: model.displayName - color: TimelineManager.userColor(model ? model.mxid : "", colors.window) + color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) font.pointSize: 12 } Label { text: model.mxid - color: colors.buttonText + color: Nheko.colors.buttonText font.pointSize: 10 } -- cgit 1.5.1 From 73d902611d583e6b57319bfbbf5071e7967a4ff0 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 18:23:21 -0400 Subject: Actually set invitees property --- resources/qml/Root.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 3825fb89..31d00585 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -155,7 +155,8 @@ Page { onOpenInviteUsersDialog: { var dialog = inviteDialog.createObject(timelineRoot, { "roomId": Rooms.currentRoom.roomId, - "roomName": Rooms.currentRoom.roomName + "roomName": Rooms.currentRoom.roomName, + "invitees": invitees }); dialog.show(); } -- cgit 1.5.1 From f30c3db86058f2700a1e2331bb1167d2ebf88f4d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 18:24:04 -0400 Subject: Fix loaded member count --- src/MemberList.cpp | 2 +- src/MemberList.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 7f4dfa99..b2ed82bd 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -96,6 +96,6 @@ MemberList::fetchMore(const QModelIndex &) { auto members = cache::getMembers(room_id_.toStdString(), rowCount()); addUsers(members); - numUsersLoaded_ = members.size(); + numUsersLoaded_ += members.size(); emit numUsersLoadedChanged(); } diff --git a/src/MemberList.h b/src/MemberList.h index 80ab834c..aa240b7a 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -58,5 +58,5 @@ private: QVector> m_memberList; QString room_id_; RoomInfo info_; - int numUsersLoaded_; + int numUsersLoaded_{0}; }; -- cgit 1.5.1 From 155315ecbb84cf517d1daa98c76ea7eb41741086 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 18:24:38 -0400 Subject: Fix Ctrl-Enter shortcut --- resources/qml/InviteDialog.qml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 83471658..3415a93e 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -29,11 +29,9 @@ ApplicationWindow { height: 380 width: 340 - // TODO: make this work in the TextField - Shortcut { - sequence: "Ctrl+Enter" - onActivated: invitees.accept() - } + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && event.modifiers & Qt.ControlModifier) + Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() + Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() Shortcut { sequence: StandardKey.Cancel @@ -66,10 +64,14 @@ ApplicationWindow { } Component.onCompleted: forceActiveFocus() - Shortcut { - sequence: "Ctrl+Enter" - onActivated: invitees.accept() - } +// Shortcut { +// sequence: "Ctrl+Enter" +// onActivated: invitees.accept() +// } + + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && event.modifiers & Qt.ControlModifier) + Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() + Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() } -- cgit 1.5.1 From d2c62529117e878cb92b59039aa367909e4c3871 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 19:24:45 -0400 Subject: More shortcut stuff --- resources/qml/InviteDialog.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 3415a93e..23420fa2 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -29,10 +29,6 @@ ApplicationWindow { height: 380 width: 340 - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && event.modifiers & Qt.ControlModifier) - Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() - Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() - Shortcut { sequence: StandardKey.Cancel onActivated: inviteDialogRoot.close() @@ -43,6 +39,10 @@ ApplicationWindow { anchors.margins: 10 spacing: 10 + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) + Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() + Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() + Label { text: qsTr("User ID to invite") Layout.fillWidth: true @@ -69,7 +69,7 @@ ApplicationWindow { // onActivated: invitees.accept() // } - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && event.modifiers & Qt.ControlModifier) + Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() -- cgit 1.5.1 From 4d5950b6a7a38327ce1be88a910a474caead68b5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 19:25:09 -0400 Subject: Document bad behavior with footer and spinner --- resources/qml/RoomMembers.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index d1d60a16..da650859 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -117,7 +117,10 @@ ApplicationWindow { footer: Spinner { // This is not a wonderful solution, but it is the best way to calculate whether // all users are loaded while keeping canFetchMore() const - running: members.numUsersLoaded < members.memberCount + + // TODO: just toggling the visiblity leaves some large empty space at the bottom + // of the list. This should be fixed. + visible: members.numUsersLoaded < members.memberCount anchors.centerIn: parent } } -- cgit 1.5.1 From 67fff656b3bc13d4264dc144582699931953ffb3 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 19:43:07 -0400 Subject: Fix bad property name --- resources/qml/InviteDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 23420fa2..8bdbb767 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -166,8 +166,8 @@ ApplicationWindow { spacing: 10 Avatar { - width: Nheko.avatarsize - height: Nheko.avatarsize + width: Nheko.avatarSize + height: Nheko.avatarSize userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName -- cgit 1.5.1 From f1f5796fb8ba5b504043310dba17acd3ef2860bd Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 17 Jul 2021 19:43:35 -0400 Subject: Get Ctrl+Enter working and fix cleaning up and closing --- resources/qml/InviteDialog.qml | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 8bdbb767..417de049 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -23,12 +23,24 @@ ApplicationWindow { } } + function cleanUpAndClose() { + if (inviteeEntry.text !== "") + addInvite(); + invitees.accept(); + close(); + } + title: qsTr("Invite users to ") + roomName x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 width: 340 + Shortcut { + sequence: "Ctrl+Enter" + onActivated: cleanUpAndClose() + } + Shortcut { sequence: StandardKey.Cancel onActivated: inviteDialogRoot.close() @@ -39,10 +51,6 @@ ApplicationWindow { anchors.margins: 10 spacing: 10 - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) - Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() - Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() - Label { text: qsTr("User ID to invite") Layout.fillWidth: true @@ -64,14 +72,8 @@ ApplicationWindow { } Component.onCompleted: forceActiveFocus() -// Shortcut { -// sequence: "Ctrl+Enter" -// onActivated: invitees.accept() -// } - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) - Keys.onEnterPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() - Keys.onReturnPressed: if (event.modifiers & Qt.ControlModifier) invitees.accept() + Keys.onPressed: if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) cleanUpAndClose() } @@ -208,10 +210,7 @@ ApplicationWindow { Button { text: qsTr("Invite") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole - onClicked: { - invitees.accept(); - inviteDialogRoot.close(); - } + onClicked: cleanUpAndClose() } Button { -- cgit 1.5.1 From 74d493ff1672eb9f8cf333bb5c11f80eb178eeb5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 10:07:54 -0400 Subject: Use standardized padding --- resources/qml/InviteDialog.qml | 6 +++--- resources/qml/RoomMembers.qml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 417de049..02cb5e07 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -49,7 +49,7 @@ ApplicationWindow { ColumnLayout { anchors.fill: parent anchors.margins: 10 - spacing: 10 + spacing: Nheko.paddingMedium Label { text: qsTr("User ID to invite") @@ -57,7 +57,7 @@ ApplicationWindow { } RowLayout { - spacing: 10 + spacing: Nheko.paddingMedium MatrixTextField { id: inviteeEntry @@ -165,7 +165,7 @@ ApplicationWindow { model: invitees delegate: RowLayout { - spacing: 10 + spacing: Nheko.paddingMedium Avatar { width: Nheko.avatarSize diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index da650859..f9be95a1 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -29,7 +29,7 @@ ApplicationWindow { ColumnLayout { anchors.fill: parent anchors.margins: 10 - spacing: 10 + spacing: Nheko.paddingMedium Avatar { id: roomAvatar @@ -81,7 +81,7 @@ ApplicationWindow { } delegate: RowLayout { - spacing: 10 + spacing: Nheko.paddingMedium Avatar { width: Nheko.avatarSize -- cgit 1.5.1 From 4384554587c3e9327382f2f9cbc36e893fbe4dab Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 12:31:20 -0400 Subject: Only invite if there is something/someone to invite --- resources/qml/InviteDialog.qml | 81 +++--------------------------------------- src/InviteesModel.cpp | 2 ++ src/InviteesModel.h | 3 ++ 3 files changed, 9 insertions(+), 77 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 02cb5e07..94a95861 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -18,13 +18,11 @@ ApplicationWindow { if (inviteeEntry.text.match("@.+?:.{3,}")) { invitees.addUser(inviteeEntry.text); inviteeEntry.clear(); - } else { - warningLabel.show(); } } function cleanUpAndClose() { - if (inviteeEntry.text !== "") + if (inviteeEntry.text.match("@.+?:.{3,}")) addInvite(); invitees.accept(); close(); @@ -79,80 +77,8 @@ ApplicationWindow { Button { text: qsTr("Add") - onClicked: { - if (inviteeEntry.text !== "") { - addInvite(); - } - } - } - - } - - Label { - id: warningLabel - - function show() { - state = "shown"; - warningLabelTimer.start(); - } - - text: qsTr("Please enter a valid username (e.g. @joe:matrix.org).") - color: "red" - visible: false - opacity: 0 - state: "hidden" - states: [ - State { - name: "shown" - - PropertyChanges { - target: warningLabel - opacity: 1 - visible: true - } - - }, - State { - name: "hidden" - - PropertyChanges { - target: warningLabel - opacity: 0 - visible: false - } - - } - ] - transitions: [ - Transition { - from: "shown" - to: "hidden" - reversible: true - - SequentialAnimation { - NumberAnimation { - target: warningLabel - property: "opacity" - duration: 500 - } - - PropertyAction { - target: warningLabel - property: "visible" - } - - } - - } - ] - - Timer { - id: warningLabelTimer - - interval: 2000 - repeat: false - running: false - onTriggered: warningLabel.state = "hidden" + enabled: inviteeEntry.text.match("@.+?:.{3,}") + onClicked: addInvite() } } @@ -210,6 +136,7 @@ ApplicationWindow { Button { text: qsTr("Invite") DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + enabled: invitees.count > 0 onClicked: cleanUpAndClose() } diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 59054690..9b64f57c 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -22,6 +22,8 @@ InviteesModel::addUser(QString mxid) connect(invitee, &Invitee::userInfoLoaded, this, [this]() { endInsertRows(); }); invitees_.push_back(invitee); + + emit countChanged(); } QHash diff --git a/src/InviteesModel.h b/src/InviteesModel.h index ac9208a0..a4e19ebb 100644 --- a/src/InviteesModel.h +++ b/src/InviteesModel.h @@ -30,6 +30,8 @@ class InviteesModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(int count READ rowCount NOTIFY countChanged) + public: enum Roles { @@ -52,6 +54,7 @@ public: signals: void accept(); + void countChanged(); private: QVector invitees_; -- cgit 1.5.1 From b6d4e6b20a5635b4acdee6e417dce0f55eaaaaa9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 12:31:57 -0400 Subject: Drop unnecessary code --- resources/qml/RoomMembers.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index f9be95a1..28e999db 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -58,7 +58,6 @@ ApplicationWindow { } ScrollView { - clip: false palette: Nheko.colors padding: 10 ScrollBar.horizontal.visible: false -- cgit 1.5.1 From e91b3067a168f9729a9d4a353ff3e2ba83a473db Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 12:32:09 -0400 Subject: Fix visibility of spinner --- resources/qml/RoomMembers.qml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 28e999db..0d957165 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -114,12 +114,9 @@ ApplicationWindow { } footer: Spinner { - // This is not a wonderful solution, but it is the best way to calculate whether - // all users are loaded while keeping canFetchMore() const - - // TODO: just toggling the visiblity leaves some large empty space at the bottom - // of the list. This should be fixed. visible: members.numUsersLoaded < members.memberCount + // use the default height if it's visible, otherwise no height at all + height: visible ? undefined : 0 anchors.centerIn: parent } } -- cgit 1.5.1 From 10c6f2b43fa88830e4a693e8a71734ab90c053c2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 12:33:54 -0400 Subject: Use Nico's favored formatting (*shrugs*) --- src/MemberList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MemberList.cpp b/src/MemberList.cpp index b2ed82bd..dd5997f5 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -60,7 +60,7 @@ MemberList::addUsers(const std::vector &members) QHash MemberList::roleNames() const { - return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}}; + return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"},}; } QVariant -- cgit 1.5.1 From 48669302ece255010f31150543d8e0ef4830fb74 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 14:31:08 -0400 Subject: make lint --- resources/qml/InviteDialog.qml | 13 ++++++++----- resources/qml/RoomMembers.qml | 8 +++++++- resources/qml/Root.qml | 3 +++ src/MemberList.cpp | 6 +++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 94a95861..b1b1bb39 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -24,6 +24,7 @@ ApplicationWindow { function cleanUpAndClose() { if (inviteeEntry.text.match("@.+?:.{3,}")) addInvite(); + invitees.accept(); close(); } @@ -64,15 +65,17 @@ ApplicationWindow { placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") Layout.fillWidth: true onAccepted: { - if (text !== "") { + if (text !== "") addInvite(); - } + } Component.onCompleted: forceActiveFocus() - Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) - Keys.onPressed: if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) cleanUpAndClose() - + Keys.onPressed: { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) { + cleanUpAndClose(); + } + } } Button { diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 0d957165..57353132 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +import "./ui" import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Window 2.12 import im.nheko 1.0 -import "./ui" ApplicationWindow { id: roomMembersRoot @@ -110,7 +110,9 @@ ApplicationWindow { Layout.fillHeight: true Layout.fillWidth: true } + } + } footer: Spinner { @@ -119,12 +121,16 @@ ApplicationWindow { height: visible ? undefined : 0 anchors.centerIn: parent } + } + } + } footer: DialogButtonBox { standardButtons: DialogButtonBox.Ok onAccepted: roomMembersRoot.close() } + } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 31d00585..102d0411 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -52,6 +52,7 @@ Page { RoomMembers { } + } Component { @@ -77,11 +78,13 @@ Page { } } + Component { id: inviteDialog InviteDialog { } + } Shortcut { diff --git a/src/MemberList.cpp b/src/MemberList.cpp index dd5997f5..04377a0f 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -60,7 +60,11 @@ MemberList::addUsers(const std::vector &members) QHash MemberList::roleNames() const { - return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"},}; + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + }; } QVariant -- cgit 1.5.1 From a9ed83a1cef0b2ac156e784276ccd01dc2fbf7e8 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 14:34:04 -0400 Subject: Remove size_t property stuff --- src/MemberList.h | 4 ++-- src/main.cpp | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/MemberList.h b/src/MemberList.h index aa240b7a..cc3b75f7 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -12,7 +12,7 @@ class MemberList : public QAbstractListModel Q_OBJECT Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) - Q_PROPERTY(size_t memberCount READ memberCount NOTIFY memberCountChanged) + Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged) Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) @@ -35,7 +35,7 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QString roomName() const { return QString::fromStdString(info_.name); } - size_t memberCount() const { return info_.member_count; } + int memberCount() const { return info_.member_count; } QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } QString roomId() const { return room_id_; } int numUsersLoaded() const { return numUsersLoaded_; } diff --git a/src/main.cpp b/src/main.cpp index 376fc4f5..29e93d49 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -205,9 +205,6 @@ main(int argc, char *argv[]) parser.process(app); - // make sure that size_t properties will work - qRegisterMetaType("size_t"); - app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"})); http::init(); -- cgit 1.5.1 From a7bdbc2af26bc3c6af0e1061d63a479a2bacdd2e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 14:37:16 -0400 Subject: Consolidate connections --- resources/qml/Root.qml | 6 ------ resources/qml/TimelineView.qml | 10 ---------- 2 files changed, 16 deletions(-) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 102d0411..0a0f90cf 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -141,20 +141,14 @@ Page { }); membersDialog.show(); } - } - Connections { - target: Rooms.currentRoom onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { "roomSettings": settings }); roomSettings.show(); } - } - Connections { - target: Rooms.currentRoom onOpenInviteUsersDialog: { var dialog = inviteDialog.createObject(timelineRoot, { "roomId": Rooms.currentRoom.roomId, diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index f5979e14..c5cc69a6 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -249,14 +249,4 @@ Item { roomid: room ? room.roomId : "" } - Connections { - target: room - onOpenRoomSettingsDialog: { - var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": settings - }); - roomSettings.show(); - } - } - } -- cgit 1.5.1 From 21eb312f69111b8141e2ac9da5bb3871287045bd Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 14:49:57 -0400 Subject: Only run spinner while loading members --- resources/qml/RoomMembers.qml | 2 +- src/MemberList.cpp | 6 ++++++ src/MemberList.h | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 57353132..b190be07 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -116,7 +116,7 @@ ApplicationWindow { } footer: Spinner { - visible: members.numUsersLoaded < members.memberCount + visible: members.numUsersLoaded < members.memberCount && members.loadingMoreMembers // use the default height if it's visible, otherwise no height at all height: visible ? undefined : 0 anchors.centerIn: parent diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 04377a0f..415e3b57 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -98,8 +98,14 @@ MemberList::canFetchMore(const QModelIndex &) const void MemberList::fetchMore(const QModelIndex &) { + loadingMoreMembers_ = true; + emit loadingMoreMembersChanged(); + auto members = cache::getMembers(room_id_.toStdString(), rowCount()); addUsers(members); numUsersLoaded_ += members.size(); emit numUsersLoadedChanged(); + + loadingMoreMembers_ = false; + emit loadingMoreMembersChanged(); } diff --git a/src/MemberList.h b/src/MemberList.h index cc3b75f7..070666a2 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -16,6 +16,7 @@ class MemberList : public QAbstractListModel Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged) Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged) + Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged) public: enum Roles @@ -39,6 +40,7 @@ public: QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); } QString roomId() const { return room_id_; } int numUsersLoaded() const { return numUsersLoaded_; } + bool loadingMoreMembers() const { return loadingMoreMembers_; } signals: void roomNameChanged(); @@ -46,6 +48,7 @@ signals: void avatarUrlChanged(); void roomIdChanged(); void numUsersLoadedChanged(); + void loadingMoreMembersChanged(); public slots: void addUsers(const std::vector &users); @@ -59,4 +62,5 @@ private: QString room_id_; RoomInfo info_; int numUsersLoaded_{0}; + bool loadingMoreMembers_{false}; }; -- cgit 1.5.1 From 7cd4e6f1c6fd73edcdeb3cc6918f009d100d2fa1 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 15:05:36 -0400 Subject: make lint --- resources/qml/InviteDialog.qml | 4 ++-- resources/qml/Root.qml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index b1b1bb39..1eaaef8f 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -72,9 +72,9 @@ ApplicationWindow { Component.onCompleted: forceActiveFocus() Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier)) Keys.onPressed: { - if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) { + if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers === Qt.ControlModifier)) cleanUpAndClose(); - } + } } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 0a0f90cf..b5395232 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -141,14 +141,12 @@ Page { }); membersDialog.show(); } - onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { "roomSettings": settings }); roomSettings.show(); } - onOpenInviteUsersDialog: { var dialog = inviteDialog.createObject(timelineRoot, { "roomId": Rooms.currentRoom.roomId, -- cgit 1.5.1 From 6c9ac76260ebdd078c1c4b8d2fdc1eb7caef73c4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Mon, 19 Jul 2021 17:41:47 -0400 Subject: Fix roomId property --- resources/qml/MessageInput.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 415d67a7..c135aff9 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -331,7 +331,7 @@ Rectangle { image: ":/icons/icons/ui/sticky-note-solid.svg" ToolTip.visible: hovered ToolTip.text: qsTr("Stickers") - onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId(), function(row) { + onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId, function(row) { room.input.sticker(stickerPopup.model.sourceModel, row); TimelineManager.focusMessageInput(); }) -- cgit 1.5.1 From 152acdc4a5eb3604515d1b6b7645446c332de1eb Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 10:10:43 -0400 Subject: Fix hardcoded spacing/padding Another padding fix --- resources/qml/InviteDialog.qml | 4 ++-- resources/qml/RoomMembers.qml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 1eaaef8f..e80087fc 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -47,7 +47,7 @@ ApplicationWindow { ColumnLayout { anchors.fill: parent - anchors.margins: 10 + anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium Label { @@ -106,7 +106,7 @@ ApplicationWindow { } ColumnLayout { - spacing: 5 + spacing: Nheko.paddingSmall Label { text: model.displayName diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index b190be07..11bd486c 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -28,7 +28,7 @@ ApplicationWindow { ColumnLayout { anchors.fill: parent - anchors.margins: 10 + anchors.margins: Nheko.paddingMedium spacing: Nheko.paddingMedium Avatar { @@ -59,7 +59,7 @@ ApplicationWindow { ScrollView { palette: Nheko.colors - padding: 10 + padding: Nheko.paddingMedium ScrollBar.horizontal.visible: false Layout.fillHeight: true Layout.minimumHeight: 200 @@ -69,7 +69,7 @@ ApplicationWindow { id: memberList clip: true - spacing: 8 + spacing: Nheko.paddingMedium boundsBehavior: Flickable.StopAtBounds model: members @@ -92,7 +92,7 @@ ApplicationWindow { } ColumnLayout { - spacing: 5 + spacing: Nheko.paddingSmall Label { text: model.displayName -- cgit 1.5.1 From fa06881c49a48c69223ceac7ad3d06105db9ed50 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 12:53:16 -0400 Subject: Don't hardcode fonts Fix hardcoded fonts (again) --- resources/qml/InviteDialog.qml | 4 ++-- resources/qml/RoomMembers.qml | 6 +++--- resources/qml/RoomSettings.qml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index e80087fc..d0e6a645 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -111,13 +111,13 @@ ApplicationWindow { Label { text: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) - font.pointSize: 12 + font.pointSize: fontMetrics.font.pointSize } Label { text: model.mxid color: Nheko.colors.buttonText - font.pointSize: 10 + font.pointSize: fontMetrics.font.pointSize * 0.9 } Item { diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 11bd486c..b9b800c5 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -43,7 +43,7 @@ ApplicationWindow { } Label { - font.pixelSize: 24 + font.pixelSize: fontMetrics.font.pixelSize * 2 text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName Layout.alignment: Qt.AlignHCenter } @@ -97,13 +97,13 @@ ApplicationWindow { Label { text: model.displayName color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) - font.pointSize: 12 + font.pointSize: fontMetrics.font.pointSize } Label { text: model.mxid color: Nheko.colors.buttonText - font.pointSize: 10 + font.pointSize: fontMetrics.font.pointSize * 0.9 } Item { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index a27be13e..2701edf9 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -98,7 +98,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomName - font.pixelSize: 24 + font.pixelSize: fontMetrics.font.pixelSize * 2 Layout.alignment: Qt.AlignHCenter } @@ -264,7 +264,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomId - font.pixelSize: 14 + font.pixelSize: fontMetrics.font.pixelSize * 1.2 Layout.alignment: Qt.AlignRight } @@ -274,7 +274,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomVersion - font.pixelSize: 14 + font.pixelSize: fontMetrics.font.pixelSize * 1.2 Layout.alignment: Qt.AlignRight } -- cgit 1.5.1 From 75920925dc455157fd4a58f3a1e53b1fc6b86ad7 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 18:04:19 -0400 Subject: Use correct colors --- resources/qml/InviteDialog.qml | 2 ++ resources/qml/RoomMembers.qml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index d0e6a645..1be2e06c 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -34,6 +34,8 @@ ApplicationWindow { y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 width: 340 + palette: Nheko.colors + color: Nheko.colors.window Shortcut { sequence: "Ctrl+Enter" diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index b9b800c5..1cf41e13 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -20,6 +20,8 @@ ApplicationWindow { height: 650 width: 420 minimumHeight: 420 + palette: Nheko.colors + color: Nheko.colors.window Shortcut { sequence: StandardKey.Cancel -- cgit 1.5.1 From 92fdda84236153b31f3c0dcd393c9db146b97b96 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 18:27:16 -0400 Subject: Use elided label --- resources/qml/RoomMembers.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 1cf41e13..09c7391e 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -44,9 +44,9 @@ ApplicationWindow { onClicked: Rooms.currentRoom.openRoomSettings(members.roomId) } - Label { + ElidedLabel { font.pixelSize: fontMetrics.font.pixelSize * 2 - text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName + fullText: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName Layout.alignment: Qt.AlignHCenter } -- cgit 1.5.1 From 823e74039698daac8bd45bf13c71b193c60ea226 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 18:27:29 -0400 Subject: Check with regex everywhere --- resources/qml/InviteDialog.qml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 1be2e06c..026e3297 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -15,14 +15,14 @@ ApplicationWindow { property InviteesModel invitees function addInvite() { - if (inviteeEntry.text.match("@.+?:.{3,}")) { + if (inviteeEntry.isValidMxid) { invitees.addUser(inviteeEntry.text); inviteeEntry.clear(); } } function cleanUpAndClose() { - if (inviteeEntry.text.match("@.+?:.{3,}")) + if (inviteeEntry.isValidMxid) addInvite(); invitees.accept(); @@ -63,11 +63,13 @@ ApplicationWindow { MatrixTextField { id: inviteeEntry + property bool isValidMxid: text.match("@.+?:.{3,}") + backgroundColor: Nheko.colors.window placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.") Layout.fillWidth: true onAccepted: { - if (text !== "") + if (isValidMxid) addInvite(); } @@ -82,7 +84,7 @@ ApplicationWindow { Button { text: qsTr("Add") - enabled: inviteeEntry.text.match("@.+?:.{3,}") + enabled: inviteeEntry.isValidMxid onClicked: addInvite() } -- cgit 1.5.1 From 44d2818e0cb2bceb25e29edf7ef32d02ede42432 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:17:20 -0400 Subject: Add property for plain room name --- resources/qml/InviteDialog.qml | 4 ++-- resources/qml/Root.qml | 2 +- src/timeline/TimelineModel.cpp | 3 +++ src/timeline/TimelineModel.h | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 026e3297..e9ff475d 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -11,7 +11,7 @@ ApplicationWindow { id: inviteDialogRoot property string roomId - property string roomName + property string plainRoomName property InviteesModel invitees function addInvite() { @@ -29,7 +29,7 @@ ApplicationWindow { close(); } - title: qsTr("Invite users to ") + roomName + title: qsTr("Invite users to ") + plainRoomName x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index b5395232..f71c18e2 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -150,7 +150,7 @@ Page { onOpenInviteUsersDialog: { var dialog = inviteDialog.createObject(timelineRoot, { "roomId": Rooms.currentRoom.roomId, - "roomName": Rooms.currentRoom.roomName, + "plainRoomName": Rooms.currentRoom.plainRoomName, "invitees": invitees }); dialog.show(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 7ce0e98a..e431e1ac 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -327,6 +327,9 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj this->isSpace_ = create->content.type == mtx::events::state::room_type::space; this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); + // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it needs to be + connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); + connect( this, &TimelineModel::redactionFailed, diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index ebf24bec..0d1eb1f9 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -162,6 +162,7 @@ class TimelineModel : public QAbstractListModel bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged) Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged) @@ -367,6 +368,7 @@ signals: void encryptionChanged(); void roomNameChanged(); + void plainRoomNameChanged(); void roomTopicChanged(); void roomAvatarUrlChanged(); void roomMemberCountChanged(); -- cgit 1.5.1 From 705c283dcb3b5eb1002c699a53f609d1b5c71a9e Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:17:31 -0400 Subject: Fix bad connection --- resources/qml/InviteDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index e9ff475d..dbe8bb07 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -106,7 +106,7 @@ ApplicationWindow { userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName - onClicked: TimelineManager.timeline.openUserProfile(model.mxid) + onClicked: Rooms.currentRoom.openUserProfile(model.mxid) } ColumnLayout { -- cgit 1.5.1 From c78c2848988cd7d0c0bb51209359f6b925d2da34 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:17:49 -0400 Subject: Call the correct function --- resources/qml/RoomMembers.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 09c7391e..8431dc99 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -56,7 +56,7 @@ ApplicationWindow { hoverEnabled: true ToolTip.visible: hovered ToolTip.text: qsTr("Invite more people") - onClicked: Rooms.currentRoom.openInviteUsersDialog() + onClicked: Rooms.currentRoom.openInviteUsers() } ScrollView { -- cgit 1.5.1 From 77c636f3d3f0bc11c2fc5e6cc90ee58bd42ab5f9 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:20:55 -0400 Subject: Insert user before loading avatar/display name --- src/InviteesModel.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 9b64f57c..7bc2b2d4 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -19,10 +19,12 @@ InviteesModel::addUser(QString mxid) beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); auto invitee = new Invitee{mxid, this}; - connect(invitee, &Invitee::userInfoLoaded, this, [this]() { endInsertRows(); }); + auto indexOfInvitee = invitees_.count(); + connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); }); invitees_.push_back(invitee); + endInsertRows(); emit countChanged(); } -- cgit 1.5.1 From efda94ca50c3942d0eb3165ce30490edd4823462 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:21:04 -0400 Subject: Modify message to be more accurate --- src/InviteesModel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index 7bc2b2d4..f73fddd9 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -69,7 +69,8 @@ Invitee::Invitee(const QString &mxid, QObject *parent) mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) { if (err) { - nhlog::net()->warn("failed to retrieve own profile info"); + nhlog::net()->warn("failed to retrieve profile info"); + emit userInfoLoaded(); return; } -- cgit 1.5.1 From 6458614ea11d6ce25936e7ca1ac550eedfa9942a Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:41:12 -0400 Subject: make lint --- src/InviteesModel.cpp | 6 ++++-- src/timeline/TimelineModel.cpp | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp index f73fddd9..27b2116f 100644 --- a/src/InviteesModel.cpp +++ b/src/InviteesModel.cpp @@ -18,9 +18,11 @@ InviteesModel::addUser(QString mxid) { beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count()); - auto invitee = new Invitee{mxid, this}; + auto invitee = new Invitee{mxid, this}; auto indexOfInvitee = invitees_.count(); - connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); }); + connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() { + emit dataChanged(index(indexOfInvitee), index(indexOfInvitee)); + }); invitees_.push_back(invitee); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e431e1ac..66d931fd 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -327,7 +327,8 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj this->isSpace_ = create->content.type == mtx::events::state::room_type::space; this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString()); - // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it needs to be + // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it + // needs to be connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged); connect( -- cgit 1.5.1 From 38c6aa65fa3e4230a4e1de9cc8e7e2204d3896ed Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:45:28 -0400 Subject: Fix elided width --- resources/qml/RoomMembers.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 8431dc99..6f847ccc 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -48,6 +48,7 @@ ApplicationWindow { font.pixelSize: fontMetrics.font.pixelSize * 2 fullText: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName Layout.alignment: Qt.AlignHCenter + elideWidth: parent.width - Nheko.paddingMedium } ImageButton { -- cgit 1.5.1 From d33538316c81b548e9b6724afc3486c17ad925f2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 20 Jul 2021 19:57:36 -0400 Subject: Fix the loading spinner setup This fixes binding loops and gives it a (in my opinion) sane size. --- resources/qml/RoomMembers.qml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 6f847ccc..3758cb0b 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -118,11 +118,21 @@ ApplicationWindow { } - footer: Spinner { - visible: members.numUsersLoaded < members.memberCount && members.loadingMoreMembers + footer: Item { + width: parent.width + visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers + // use the default height if it's visible, otherwise no height at all - height: visible ? undefined : 0 - anchors.centerIn: parent + height: membersLoadingSpinner.height + anchors.margins: Nheko.paddingMedium + + Spinner { + id: membersLoadingSpinner + + anchors.centerIn: parent + height: visible ? 35 : 0 + } + } } -- cgit 1.5.1 From 5e85fa606ea800dd524547dd1eb9019498b3929e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 13:55:29 +0200 Subject: Fix color of invite label --- resources/qml/InviteDialog.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index dbe8bb07..c6b42d29 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -55,6 +55,7 @@ ApplicationWindow { Label { text: qsTr("User ID to invite") Layout.fillWidth: true + color: Nheko.colors.text } RowLayout { -- cgit 1.5.1 From 8a300f733303bac95c3c41839bc553d1cfd2786f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 16:55:28 +0200 Subject: Fix some media not being cached properly Most noticeable in the goose chooser. --- src/MxcImageProvider.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index ab6540a4..ab0f8152 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -196,7 +196,6 @@ MxcImageProvider::download(const QString &id, image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); - image.save(fileInfo.absoluteFilePath()); then(id, requestedSize, image, fileInfo.absoluteFilePath()); }); } catch (std::exception &e) { -- cgit 1.5.1 From f401ed16ace26dbe397fa2dce59fbb2f455a35f7 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 17:44:29 +0200 Subject: Tweak rainbows slightly --- src/Utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 8d5ae4a9..484b45cf 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -556,7 +556,7 @@ utils::markdownToHtml(const QString &text, bool rainbowify) // Use colors as described here: // https://shark.comfsm.fm/~dleeling/cis/hsl_rainbow.html auto color = - QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 1.0, 0.5); + QColor::fromHslF((charIdx - 1.0) / textLen * (5. / 6.), 0.9, 0.5); // format color for HTML auto colorString = color.name(QColor::NameFormat::HexRgb); // create HTML element for current char -- cgit 1.5.1 From b17002929c2e968835b510bd47c02d9df2461bc3 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 21 Jul 2021 10:08:04 -0400 Subject: Open room members when member info label clicked --- resources/qml/RoomSettings.qml | 12 +++++++++++- src/MemberList.cpp | 2 +- src/MemberList.h | 2 +- src/timeline/TimelineModel.cpp | 4 ++-- src/timeline/TimelineModel.h | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 2701edf9..8746d4d3 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -4,7 +4,7 @@ import "./ui" import Qt.labs.platform 1.1 as Platform -import QtQuick 2.9 +import QtQuick 2.15 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 @@ -105,6 +105,16 @@ ApplicationWindow { MatrixText { text: qsTr("%1 member(s)").arg(roomSettings.memberCount) Layout.alignment: Qt.AlignHCenter + + TapHandler { + onTapped: Rooms.currentRoom.openRoomMembers(roomSettings.roomId) + } + + CursorShape { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + } + } } diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 415e3b57..0ef3b696 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -22,7 +22,7 @@ #include "timeline/TimelineViewManager.h" #include "ui/Avatar.h" -MemberList::MemberList(const QString &room_id, QWidget *parent) +MemberList::MemberList(const QString &room_id, QObject *parent) : QAbstractListModel{parent} , room_id_{room_id} { diff --git a/src/MemberList.h b/src/MemberList.h index 070666a2..9932f6a4 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -25,7 +25,7 @@ public: DisplayName, AvatarUrl, }; - MemberList(const QString &room_id, QWidget *parent = nullptr); + MemberList(const QString &room_id, QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 66d931fd..e9fa4a05 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1067,9 +1067,9 @@ TimelineModel::openUserProfile(QString userid) } void -TimelineModel::openRoomMembers() +TimelineModel::openRoomMembers(QString room_id) { - MemberList *memberList = new MemberList(roomId()); + MemberList *memberList = new MemberList(room_id == QString() ? roomId() : room_id, this); emit openRoomMembersDialog(memberList); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0d1eb1f9..077245cb 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -239,7 +239,7 @@ public: Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid); - Q_INVOKABLE void openRoomMembers(); + Q_INVOKABLE void openRoomMembers(QString room_id = QString()); Q_INVOKABLE void openRoomSettings(QString room_id = QString()); Q_INVOKABLE void openInviteUsers(QString roomId = QString()); Q_INVOKABLE void editAction(QString id); -- cgit 1.5.1 From d6ccb6e307aaacdab073f0fa6eb3fa6e269a7644 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 19:13:34 +0200 Subject: Update translations --- resources/langs/nheko_cs.ts | 251 ++++++++++++++++++++++++---------------- resources/langs/nheko_de.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_el.ts | 248 ++++++++++++++++++++++++---------------- resources/langs/nheko_en.ts | 252 +++++++++++++++++++++++++---------------- resources/langs/nheko_eo.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_es.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_et.ts | 252 +++++++++++++++++++++++++---------------- resources/langs/nheko_fi.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_fr.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_hu.ts | 251 ++++++++++++++++++++++++---------------- resources/langs/nheko_it.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_ja.ts | 249 ++++++++++++++++++++++++---------------- resources/langs/nheko_ml.ts | 252 +++++++++++++++++++++++++---------------- resources/langs/nheko_nl.ts | 248 ++++++++++++++++++++++++---------------- resources/langs/nheko_pl.ts | 251 ++++++++++++++++++++++++---------------- resources/langs/nheko_pt_BR.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_pt_PT.ts | 250 ++++++++++++++++++++++++---------------- resources/langs/nheko_ro.ts | 251 ++++++++++++++++++++++++---------------- resources/langs/nheko_ru.ts | 251 ++++++++++++++++++++++++---------------- resources/langs/nheko_si.ts | 248 ++++++++++++++++++++++++---------------- resources/langs/nheko_sv.ts | 252 +++++++++++++++++++++++++---------------- resources/langs/nheko_zh_CN.ts | 249 ++++++++++++++++++++++++---------------- resources/qml/InviteDialog.qml | 2 +- resources/qml/RoomMembers.qml | 4 +- 24 files changed, 3328 insertions(+), 2183 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index c544bf6d..7c1134aa 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,43 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1463,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1495,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1570,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1590,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1619,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1630,7 @@ Example: https://server.my:8787 - + Save image @@ -1713,12 +1765,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1747,7 +1799,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1755,17 +1807,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1773,7 +1840,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1781,18 +1848,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1833,7 +1899,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1843,7 +1909,7 @@ Example: https://server.my:8787 - + Verify @@ -1892,7 +1958,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1901,7 +1967,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2347,7 +2413,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2398,7 +2464,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2469,19 +2535,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 58c209a2..3cd35781 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Auf Bestätigung warten @@ -48,7 +48,7 @@ Wartet darauf, dass die andere Seite die Verifizierung abschließt. - + Cancel Abbrechen @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nutzer konnte nicht eingeladen werden: %1 @@ -157,12 +157,12 @@ - + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Geheimnisse entschlüsseln @@ -426,12 +426,12 @@ EmojiPicker - + Search Suche - + People Leute @@ -607,7 +607,7 @@ InputBar - + Select a file Datei auswählen @@ -617,17 +617,43 @@ Alle Dateien (*) - + Failed to upload media. Please try again. Medienupload fehlgeschlagen. Bitte versuche es erneut. - InviteeItem + InviteDialog + + + Invite users to %1 + Lade Benutzer in %1 ein + + + + User ID to invite + Benutzer-ID, die eingeladen werden soll + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + @joe:matrix.org + + + + Add + Hinzufügen + - - Remove - Löschen + + Invite + Einladen + + + + Cancel + Abbrechen @@ -744,23 +770,10 @@ Beispiel: https://mein.server:8787 SSO Anmeldung fehlgeschlagen - - MemberList - - - Room members - Teilnehmerliste - - - - OK - OK - - MessageDelegate - + removed entfernt @@ -771,7 +784,7 @@ Beispiel: https://mein.server:8787 Verschlüsselung aktiviert - + room name changed to: %1 Raumname wurde gändert auf: %1 @@ -781,7 +794,7 @@ Beispiel: https://mein.server:8787 Raumname wurde entfernt - + topic changed to: %1 Raumthema wurde geändert auf: %1 @@ -791,17 +804,17 @@ Beispiel: https://mein.server:8787 Thema wurde entfernt - + %1 changed the room avatar %1 hat dem Raumavatar geändert - + %1 created and configured room: %2 %1 hat den Raum erstellt: %2 - + %1 placed a voice call. %1 hat einen Sprachanruf gestartet. @@ -816,17 +829,17 @@ Beispiel: https://mein.server:8787 %1 hat angerufen. - + %1 answered the call. %1 hat den Anruf angenommen. - + %1 ended the call. %1 hat den Anruf beendet. - + Negotiating call... Wählt… @@ -834,7 +847,7 @@ Beispiel: https://mein.server:8787 MessageInput - + Hang up Auflegen @@ -855,6 +868,11 @@ Beispiel: https://mein.server:8787 + Stickers + Sticker + + + Emoji Emoji @@ -872,17 +890,17 @@ Beispiel: https://mein.server:8787 MessageView - + Edit Bearbeiten - + React Reaktion senden - + Reply Antworten @@ -892,7 +910,7 @@ Beispiel: https://mein.server:8787 Optionen - + &Copy &Kopieren @@ -1100,7 +1118,7 @@ Beispiel: https://mein.server:8787 Placeholder - + unimplemented event: Unimplementiertes Event: @@ -1220,7 +1238,7 @@ Beispiel: https://mein.server:8787 ReplyPopup - + Close Schließen @@ -1233,7 +1251,7 @@ Beispiel: https://mein.server:8787 RoomInfo - + no version stored keine Version gespeichert @@ -1241,7 +1259,7 @@ Beispiel: https://mein.server:8787 RoomList - + New tag Neuer Tag @@ -1281,17 +1299,7 @@ Beispiel: https://mein.server:8787 Neuen Tag erstellen... - - Accept - Akzeptieren - - - - Decline - Ablehnen - - - + Status Message Statusnachricht @@ -1341,20 +1349,42 @@ Beispiel: https://mein.server:8787 Benutzereinstellungen + + RoomMembers + + + Members of %1 + Teilnehmer in %1 + + + + %n people in %1 + Summary above list of members + + %n Person in %1 + %n Personen in %1 + + + + + Invite more people + Lade mehr Leute ein + + RoomSettings - + Room Settings Raumeinstellungen - + %1 member(s) %1 Teilnehmer - + SETTINGS EINSTELLUNGEN @@ -1436,11 +1466,6 @@ Beispiel: https://mein.server:8787 Room Version Raumversion - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1498,24 @@ Beispiel: https://mein.server:8787 Hochladen des Bildes fehlgeschlagen: %s + + RoomlistModel + + + Pending invite. + Offene Einladung. + + + + Previewing this room + Vorschau dieses Raums + + + + No preview available + Keine Vorschau verfügbar + + ScreenShare @@ -1530,7 +1573,7 @@ Beispiel: https://mein.server:8787 StatusIndicator - + Failed Fehlgeschlagen @@ -1550,6 +1593,14 @@ Beispiel: https://mein.server:8787 Gelesen + + StickerPicker + + + Search + Suche + + Success @@ -1571,7 +1622,7 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 @@ -1582,7 +1633,7 @@ Beispiel: https://mein.server:8787 Event konnte nicht verschlüsselt werden, senden wurde abgebrochen! - + Save image Bild speichern @@ -1716,12 +1767,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. @@ -1750,7 +1801,7 @@ Beispiel: https://mein.server:8787 TimelineRow - + Edited Bearbeitet @@ -1758,17 +1809,32 @@ Beispiel: https://mein.server:8787 TimelineView - + No room open Kein Raum geöffnet - + %1 member(s) %1 Teilnehmer - + + join the conversation + An der Unterhaltung teilnehmen + + + + accept invite + Einladung annehmen + + + + decline invite + Einladung ablehnen + + + Back to room list Zurück zur Raumliste @@ -1776,7 +1842,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -1784,18 +1850,17 @@ Beispiel: https://mein.server:8787 TopBar - + Back to room list Zurück zur Raumliste - - + No room selected Kein Raum ausgewählt - + Room options Raumoptionen @@ -1836,7 +1901,7 @@ Beispiel: https://mein.server:8787 UserProfile - + Global User Profile Globales Nutzerprofil @@ -1846,7 +1911,7 @@ Beispiel: https://mein.server:8787 Raumspezifisches Nutzerprofil - + Verify Verifizieren @@ -1895,7 +1960,7 @@ Beispiel: https://mein.server:8787 UserSettings - + Default Standard @@ -1904,7 +1969,7 @@ Beispiel: https://mein.server:8787 UserSettingsPage - + Minimize to tray Ins Benachrichtigungsfeld minimieren @@ -2360,7 +2425,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. Waiting - + Waiting for other party… Auf Gegenseite warten… @@ -2411,7 +2476,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. descriptiveTime - + Yesterday Gestern @@ -2482,19 +2547,6 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein.Öffne das Fallback, folge den Anweisungen und bestätige nach Abschluss via "Bestätigen". - - dialogs::InviteUsers - - - Cancel - Abbrechen - - - - User ID to invite - Benutzer-ID, die eingeladen werden soll - - dialogs::JoinRoom diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index d5d5f323..6df73f73 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel Άκυρο @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file Διάλεξε ένα αρχείο @@ -617,18 +617,44 @@ Όλα τα αρχεία (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Όνομα χρήστη + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove + + Invite + + + Cancel + Άκυρο + LoginPage @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Μέλη - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Αποδοχή - - - - Decline - Απόρριψη - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Αποθήκευση Εικόνας @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Ελαχιστοποίηση @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Άκυρο - - - - User ID to invite - Όνομα χρήστη - - dialogs::JoinRoom diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 7d3f8276..97a67b06 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Awaiting Confirmation @@ -48,7 +48,7 @@ Waiting for other side to complete verification. - + Cancel Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Search - + People People @@ -607,7 +607,7 @@ InputBar - + Select a file Select a file @@ -617,17 +617,43 @@ All Files (*) - + Failed to upload media. Please try again. Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + Invite users to %1 + + + + User ID to invite + User ID to invite + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + @joe:matrix.org + + + + Add + Add + - - Remove - Remove + + Invite + Invite + + + + Cancel + Cancel @@ -744,28 +770,15 @@ Example: https://server.my:8787 SSO login failed - - MemberList - - - Room members - Room members - - - - OK - OK - - MessageDelegate - + Encryption enabled Encryption enabled - + room name changed to: %1 room name changed to: %1 @@ -775,7 +788,7 @@ Example: https://server.my:8787 removed room name - + topic changed to: %1 topic changed to: %1 @@ -785,17 +798,17 @@ Example: https://server.my:8787 removed topic - + %1 changed the room avatar %1 changed the room avatar - + %1 created and configured room: %2 %1 created and configured room: %2 - + %1 placed a voice call. %1 placed a voice call. @@ -810,23 +823,23 @@ Example: https://server.my:8787 %1 placed a call. - + Negotiating call... Negotiating call… - + %1 answered the call. %1 answered the call. - + removed removed - + %1 ended the call. %1 ended the call. @@ -834,7 +847,7 @@ Example: https://server.my:8787 MessageInput - + Hang up Hang up @@ -855,6 +868,11 @@ Example: https://server.my:8787 + Stickers + Stickers + + + Emoji Emoji @@ -872,17 +890,17 @@ Example: https://server.my:8787 MessageView - + Edit Edit - + React React - + Reply Reply @@ -892,7 +910,7 @@ Example: https://server.my:8787 Options - + &Copy &Copy @@ -1100,7 +1118,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: unimplemented event: @@ -1220,7 +1238,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Close @@ -1233,7 +1251,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -1241,7 +1259,7 @@ Example: https://server.my:8787 RoomList - + New tag New tag @@ -1281,17 +1299,7 @@ Example: https://server.my:8787 Create new tag... - - Accept - Accept - - - - Decline - Decline - - - + Status Message Status Message @@ -1341,20 +1349,42 @@ Example: https://server.my:8787 User settings + + RoomMembers + + + Members of %1 + Members of %1 + + + + %n people in %1 + Summary above list of members + + %n person in %1 + %n people in %1 + + + + + Invite more people + Invite more people + + RoomSettings - + Room Settings Room Settings - + %1 member(s) %1 member(s) - + SETTINGS SETTINGS @@ -1438,11 +1468,6 @@ E2E implementation until device verification is completed. Room Version Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ E2E implementation until device verification is completed. Failed to upload image: %s + + RoomlistModel + + + Pending invite. + Pending invite. + + + + Previewing this room + Previewing this room + + + + No preview available + No preview available + + ScreenShare @@ -1532,7 +1575,7 @@ E2E implementation until device verification is completed. StatusIndicator - + Failed Failed @@ -1552,6 +1595,14 @@ E2E implementation until device verification is completed. Read + + StickerPicker + + + Search + Search + + Success @@ -1573,7 +1624,7 @@ E2E implementation until device verification is completed. TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 @@ -1584,7 +1635,7 @@ E2E implementation until device verification is completed. Failed to encrypt event, sending aborted! - + Save image Save image @@ -1718,12 +1769,12 @@ E2E implementation until device verification is completed. %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. @@ -1752,7 +1803,7 @@ E2E implementation until device verification is completed. TimelineRow - + Edited Edited @@ -1760,17 +1811,32 @@ E2E implementation until device verification is completed. TimelineView - + No room open No room open - + %1 member(s) %1 member(s) - + + join the conversation + join the conversation + + + + accept invite + accept invite + + + + decline invite + decline invite + + + Back to room list Back to room list @@ -1778,7 +1844,7 @@ E2E implementation until device verification is completed. TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1786,18 +1852,17 @@ E2E implementation until device verification is completed. TopBar - + Back to room list Back to room list - - + No room selected No room selected - + Room options Room options @@ -1838,7 +1903,7 @@ E2E implementation until device verification is completed. UserProfile - + Global User Profile Global User Profile @@ -1848,7 +1913,7 @@ E2E implementation until device verification is completed. Room User Profile - + Verify Verify @@ -1897,7 +1962,7 @@ E2E implementation until device verification is completed. UserSettings - + Default Default @@ -1906,7 +1971,7 @@ E2E implementation until device verification is completed. UserSettingsPage - + Minimize to tray Minimize to tray @@ -2363,7 +2428,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… Waiting for other party… @@ -2414,7 +2479,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Yesterday @@ -2485,19 +2550,6 @@ This usually causes the application icon in the task bar to animate in some fash Open the fallback, follow the steps and confirm after completing them. - - dialogs::InviteUsers - - - Cancel - Cancel - - - - User ID to invite - User ID to invite - - dialogs::JoinRoom diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index 26a67d49..f3529f28 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Atendante konfirmon @@ -48,7 +48,7 @@ Atendante kontrolon venontan de la alia flanko. - + Cancel Nuligi @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Malsukcesis inviti uzanton: %1 @@ -158,12 +158,12 @@ - + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -353,7 +353,7 @@ CrossSigningSecrets - + Decrypt secrets Malĉifri sekretojn @@ -427,12 +427,12 @@ EmojiPicker - + Search Serĉu - + People Homoj @@ -608,7 +608,7 @@ InputBar - + Select a file Elektu dosieron @@ -618,17 +618,43 @@ Ĉiuj dosieroj (*) - + Failed to upload media. Please try again. Malsukcesis alŝuti vidaŭdaĵojn. Bonvolu reprovi. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - Forigi + + Cancel + Nuligi @@ -747,23 +773,10 @@ Ekzemplo: https://servilo.mia:8787 Malsukcesis ununura saluto - - MemberList - - - Room members - Membroj de la ĉambro - - - - OK - Bone - - MessageDelegate - + removed forigita @@ -774,7 +787,7 @@ Ekzemplo: https://servilo.mia:8787 - + room name changed to: %1 Nomo da ĉambro ŝanĝiĝis al: %1 @@ -784,7 +797,7 @@ Ekzemplo: https://servilo.mia:8787 - + topic changed to: %1 @@ -794,17 +807,17 @@ Ekzemplo: https://servilo.mia:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. %1 metis voĉvokon. @@ -819,17 +832,17 @@ Ekzemplo: https://servilo.mia:8787 %1 metis vokon. - + %1 answered the call. %1 respondis la vokon. - + %1 ended the call. %1 finis la vokon. - + Negotiating call... Traktante vokon… @@ -837,7 +850,7 @@ Ekzemplo: https://servilo.mia:8787 MessageInput - + Hang up @@ -858,6 +871,11 @@ Ekzemplo: https://servilo.mia:8787 + Stickers + + + + Emoji Bildosignoj @@ -875,17 +893,17 @@ Ekzemplo: https://servilo.mia:8787 MessageView - + Edit Redakti - + React Reagi - + Reply Respondi @@ -895,7 +913,7 @@ Ekzemplo: https://servilo.mia:8787 Elektebloj - + &Copy @@ -1103,7 +1121,7 @@ Ekzemplo: https://servilo.mia:8787 Placeholder - + unimplemented event: neprogramita okazo: @@ -1223,7 +1241,7 @@ Ekzemplo: https://servilo.mia:8787 ReplyPopup - + Close Fermi @@ -1236,7 +1254,7 @@ Ekzemplo: https://servilo.mia:8787 RoomInfo - + no version stored @@ -1244,7 +1262,7 @@ Ekzemplo: https://servilo.mia:8787 RoomList - + New tag @@ -1284,17 +1302,7 @@ Ekzemplo: https://servilo.mia:8787 - - Accept - Akcepti - - - - Decline - Rifuzi - - - + Status Message @@ -1344,20 +1352,42 @@ Ekzemplo: https://servilo.mia:8787 Agordoj de uzanto + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Agordoj de ĉambro - + %1 member(s) %1 ĉambrano(j) - + SETTINGS AGORDOJ @@ -1439,11 +1469,6 @@ Ekzemplo: https://servilo.mia:8787 Room Version Versio de ĉambro - - - OK - Bone - Failed to enable encryption: %1 @@ -1476,6 +1501,24 @@ Ekzemplo: https://servilo.mia:8787 Malsukcesis alŝuti bildon: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1533,7 +1576,7 @@ Ekzemplo: https://servilo.mia:8787 StatusIndicator - + Failed Estas malsukcesa @@ -1554,6 +1597,14 @@ Ekzemplo: https://servilo.mia:8787 Estas legita + + StickerPicker + + + Search + Serĉu + + Success @@ -1575,7 +1626,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 @@ -1586,7 +1637,7 @@ Ekzemplo: https://servilo.mia:8787 - + Save image Konservi bildon @@ -1722,12 +1773,12 @@ Ekzemplo: https://servilo.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. @@ -1756,7 +1807,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineRow - + Edited Redaktita @@ -1764,17 +1815,32 @@ Ekzemplo: https://servilo.mia:8787 TimelineView - + No room open - + %1 member(s) %1 ĉambrano(j) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1782,7 +1848,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1790,18 +1856,17 @@ Ekzemplo: https://servilo.mia:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1842,7 +1907,7 @@ Ekzemplo: https://servilo.mia:8787 UserProfile - + Global User Profile @@ -1852,7 +1917,7 @@ Ekzemplo: https://servilo.mia:8787 - + Verify @@ -1901,7 +1966,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettings - + Default @@ -1910,7 +1975,7 @@ Ekzemplo: https://servilo.mia:8787 UserSettingsPage - + Minimize to tray @@ -2373,7 +2438,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2425,7 +2490,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Hieraŭ @@ -2496,19 +2561,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Nuligi - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 6318b9c4..8eb4675a 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Esperando confirmación @@ -48,7 +48,7 @@ Esperando a que la otra parte complete la verificación. - + Cancel Cancelar @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 No se pudo invitar al usuario: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,18 +617,44 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove + + Invite + + + Cancel + Cancelar + LoginPage @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Aceptar - - - - Decline - Rechazar - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1722,12 +1773,12 @@ Example: https://server.my:8787 - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Cancelar - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 198ec332..20261395 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Ootan kinnitust @@ -48,7 +48,7 @@ Ootan et teine osapool lõpetaks verifitseerimise. - + Cancel Katkesta @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kutse saatmine kasutajale ei õnnestunud: %1 @@ -157,12 +157,12 @@ - + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekrüpti andmed @@ -426,12 +426,12 @@ EmojiPicker - + Search Otsi - + People Inimesed @@ -607,7 +607,7 @@ InputBar - + Select a file Vali fail @@ -617,17 +617,43 @@ Kõik failid (*) - + Failed to upload media. Please try again. Meediafailide üleslaadimine ei õnnestunud. Palun proovi uuesti. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Kasutajatunnus, kellele soovid kutset saata + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove - Eemalda + + Invite + + + + + Cancel + @@ -744,28 +770,15 @@ Näiteks: https://server.minu:8787 Ühekordne sisselogimine ei õnnestunud - - MemberList - - - Room members - Jututoa liikmed - - - - OK - Sobib - - MessageDelegate - + Encryption enabled Krüptimine on kasutusel - + room name changed to: %1 jututoa uus nimi on: %1 @@ -775,7 +788,7 @@ Näiteks: https://server.minu:8787 eemaldas jututoa nime - + topic changed to: %1 jututoa uus teema on: %1 @@ -785,17 +798,17 @@ Näiteks: https://server.minu:8787 teema on eemaldatud - + %1 changed the room avatar %1 muutis jututoa tunnuspilti - + %1 created and configured room: %2 %1 lõi ja seadistas jututoa: %2 - + %1 placed a voice call. %1 helistas. @@ -810,23 +823,23 @@ Näiteks: https://server.minu:8787 %1 helistas. - + Negotiating call... Ühendan kõnet… - + %1 answered the call. %1 vastas kõnele. - + removed eemaldatud - + %1 ended the call. %1 lõpetas kõne. @@ -834,7 +847,7 @@ Näiteks: https://server.minu:8787 MessageInput - + Hang up Lõpeta kõne @@ -855,6 +868,11 @@ Näiteks: https://server.minu:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Näiteks: https://server.minu:8787 MessageView - + Edit Muuda - + React Reageeri - + Reply Vasta @@ -892,7 +910,7 @@ Näiteks: https://server.minu:8787 Valikud - + &Copy &Kopeeri @@ -1100,7 +1118,7 @@ Näiteks: https://server.minu:8787 Placeholder - + unimplemented event: implementeerimata sündmus: @@ -1220,7 +1238,7 @@ Näiteks: https://server.minu:8787 ReplyPopup - + Close Sulge @@ -1233,7 +1251,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1241,7 +1259,7 @@ Näiteks: https://server.minu:8787 RoomList - + New tag Uus silt @@ -1281,17 +1299,7 @@ Näiteks: https://server.minu:8787 Loo uus silt... - - Accept - Nõustu - - - - Decline - Keeldu - - - + Status Message Olekuteade @@ -1341,20 +1349,42 @@ Näiteks: https://server.minu:8787 Kasutaja seadistused + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Jututoa seadistused - + %1 member(s) %1 liige(t) - + SETTINGS SEADISTUSED @@ -1438,11 +1468,6 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Room Version Jututoa versioon - - - OK - Sobib - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Viga faili üleslaadimisel: %1 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1575,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< StatusIndicator - + Failed Ebaõnnestus @@ -1552,6 +1595,14 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Loetud + + StickerPicker + + + Search + Otsi + + Success @@ -1573,7 +1624,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 @@ -1584,7 +1635,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Sündmuse krüptimine ei õnnestunud, katkestame saatmise! - + Save image Salvesta pilt @@ -1718,12 +1769,12 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. @@ -1752,7 +1803,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineRow - + Edited Muudetud @@ -1760,17 +1811,32 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineView - + No room open Ühtegi jututuba pole avatud - + %1 member(s) %1 liige(t) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Tagasi jututubade loendisse @@ -1778,7 +1844,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -1786,18 +1852,17 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TopBar - + Back to room list Tagasi jututubade loendisse - - + No room selected Jututuba on valimata - + Room options Jututoa valikud @@ -1838,7 +1903,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserProfile - + Global User Profile Üldine kasutajaprofiil @@ -1848,7 +1913,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Kasutajaprofiil jututoas - + Verify Verifitseeri @@ -1897,7 +1962,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserSettings - + Default Vaikimisi @@ -1906,7 +1971,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< UserSettingsPage - + Minimize to tray Vähenda tegumiribale @@ -2363,7 +2428,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Waiting - + Waiting for other party… Ootan teise osapoole tegevust… @@ -2414,7 +2479,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim descriptiveTime - + Yesterday Eile @@ -2485,19 +2550,6 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim Ava kasutaja registreerimise tagavaravariant, läbi kõik sammud ja kinnita seda, kui kõik valmis on. - - dialogs::InviteUsers - - - Cancel - Tühista - - - - User ID to invite - Kasutajatunnus, kellele soovid kutset saata - - dialogs::JoinRoom diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index 9c48e98f..e884e0ac 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Odotetaan vahvistusta @@ -48,7 +48,7 @@ - + Cancel Peruuta @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Hae - + People Ihmiset @@ -607,7 +607,7 @@ InputBar - + Select a file Valitse tiedosto @@ -617,17 +617,43 @@ Kaikki Tiedostot (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Käyttäjätunnus kutsuttavaksi + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + - - Remove - Poista + + Add + + + + + Invite + + + + + Cancel + Peruuta @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Huoneen jäsenet - - - - OK - OK - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 Salaus on käytössä - + room name changed to: %1 huoneen nimi muutettu: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 %1 soitti puhelun. - + %1 answered the call. %1 vastasi puheluun. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit Muokkaa - + React Reagoi - + Reply Vastaa @@ -888,7 +906,7 @@ Example: https://server.my:8787 Asetukset - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Sulje @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored ei tallennettua versiota @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Hyväksy - - - - Decline - Hylkää - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 Käyttäjäasetukset + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version Huoneen versio - - - OK - OK - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 Kuvan lähetys epäonnistui: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + Hae + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin muokkaus epäonnistui: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Tallenna kuva @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Sinä liityit tähän huoneeseen. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Muokattu @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options Huoneen asetukset @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Pienennä ilmoitusalueelle @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Eilen @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Peruuta - - - - User ID to invite - Käyttäjätunnus kutsuttavaksi - - dialogs::JoinRoom diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index b6345d62..fb3c0e11 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Attente de confirmation @@ -48,7 +48,7 @@ Attente de la vérification par le correspondant. - + Cancel Annuler @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Échec lors de l'invitation de %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? Voulez-vous vraiment inviter %1 (%2) ? @@ -227,12 +227,12 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Déchiffrer les secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Chercher - + People Personnes @@ -607,7 +607,7 @@ InputBar - + Select a file Sélectionnez un fichier @@ -617,17 +617,43 @@ Tous les types de fichiers (*) - + Failed to upload media. Please try again. Échec de l'envoi du média. Veuillez réessayer. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Identifiant d'utilisateur à inviter + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - Retirer + + Cancel + Annuler @@ -744,23 +770,10 @@ Exemple : https ://monserveur.example.com :8787 Échec de la connexion SSO - - MemberList - - - Room members - Membres du salon - - - - OK - OK - - MessageDelegate - + removed retiré @@ -771,7 +784,7 @@ Exemple : https ://monserveur.example.com :8787 Chiffrement activé - + room name changed to: %1 nom du salon changé en : %1 @@ -781,7 +794,7 @@ Exemple : https ://monserveur.example.com :8787 nom du salon retiré - + topic changed to: %1 sujet changé pour : %1 @@ -791,17 +804,17 @@ Exemple : https ://monserveur.example.com :8787 sujet retiré - + %1 changed the room avatar - + %1 created and configured room: %2 %1 a créé et configuré le salon : %2 - + %1 placed a voice call. %1 a effectué un appel vocal. @@ -816,17 +829,17 @@ Exemple : https ://monserveur.example.com :8787 %1 a appelé. - + %1 answered the call. %1 a répondu à l'appel. - + %1 ended the call. %1 a terminé l'appel. - + Negotiating call... Négociation de l'appel… @@ -834,7 +847,7 @@ Exemple : https ://monserveur.example.com :8787 MessageInput - + Hang up Raccrocher @@ -855,6 +868,11 @@ Exemple : https ://monserveur.example.com :8787 + Stickers + + + + Emoji Émoji @@ -872,17 +890,17 @@ Exemple : https ://monserveur.example.com :8787 MessageView - + Edit Modifier - + React Réagir - + Reply Répondre @@ -892,7 +910,7 @@ Exemple : https ://monserveur.example.com :8787 Options - + &Copy @@ -1100,7 +1118,7 @@ Exemple : https ://monserveur.example.com :8787 Placeholder - + unimplemented event: Évènement non implémenté : @@ -1220,7 +1238,7 @@ Exemple : https ://monserveur.example.com :8787 ReplyPopup - + Close Fermer @@ -1233,7 +1251,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -1241,7 +1259,7 @@ Exemple : https ://monserveur.example.com :8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exemple : https ://monserveur.example.com :8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1341,20 +1349,42 @@ Exemple : https ://monserveur.example.com :8787 Paramètres utilisateur + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Configuration du salon - + %1 member(s) %1 membre(s) - + SETTINGS CONFIGURATION @@ -1438,11 +1468,6 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Room Version Version du salon - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1500,24 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Échec de l'envoi de l'image  : %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1575,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& StatusIndicator - + Failed Échec @@ -1552,6 +1595,14 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Lu + + StickerPicker + + + Search + Chercher + + Success @@ -1573,7 +1624,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineModel - + Message redaction failed: %1 Échec de la suppression du message : %1 @@ -1584,7 +1635,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Échec du chiffrement de l'évènement, envoi abandonné ! - + Save image Enregistrer l'image @@ -1718,12 +1769,12 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. @@ -1752,7 +1803,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineRow - + Edited Modifié @@ -1760,17 +1811,32 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineView - + No room open Aucun salon ouvert - + %1 member(s) %1 membre(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Revenir à la liste des salons @@ -1778,7 +1844,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -1786,18 +1852,17 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TopBar - + Back to room list Revenir à la liste des salons - - + No room selected Pas de salon sélectionné - + Room options Options du salon @@ -1838,7 +1903,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserProfile - + Global User Profile Profil général de l'utilisateur @@ -1848,7 +1913,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Profil utilisateur spécifique au salon - + Verify Vérifier @@ -1897,7 +1962,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserSettings - + Default Défaut @@ -1906,7 +1971,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& UserSettingsPage - + Minimize to tray Réduire à la barre des tâches @@ -2365,7 +2430,7 @@ Cela met l'application en évidence dans la barre des tâches. Waiting - + Waiting for other party… Attente du correspondant… @@ -2416,7 +2481,7 @@ Cela met l'application en évidence dans la barre des tâches. descriptiveTime - + Yesterday Hier @@ -2487,19 +2552,6 @@ Cela met l'application en évidence dans la barre des tâches.Ouvrez la solution de repli, suivez les étapes et confirmez après les avoir terminées. - - dialogs::InviteUsers - - - Cancel - Annuler - - - - User ID to invite - Identifiant d'utilisateur à inviter - - dialogs::JoinRoom diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index e989a6ce..a85f9ff3 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Várakozás megerősítésre @@ -48,7 +48,7 @@ Várakozás a másik oldalra a hitelesítés befejezéséhez. - + Cancel Mégse @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nem sikerült meghívni a felhasználót: %1 @@ -157,12 +157,12 @@ - + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Titkos tároló feloldása @@ -426,12 +426,12 @@ EmojiPicker - + Search Keresés - + People Emberek @@ -607,7 +607,7 @@ InputBar - + Select a file Fájl kiválasztása @@ -617,17 +617,43 @@ Minden fájl (*) - + Failed to upload media. Please try again. Nem sikerült feltölteni a médiafájlt. Kérlek, próbáld újra! - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Meghívandó felhasználó azonosítója + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - Eltávolítás + + Cancel + Mégse @@ -744,28 +770,15 @@ Példa: https://szerver.em:8787 SSO bejelentkezés nem sikerült - - MemberList - - - Room members - Szobatagok - - - - OK - OK - - MessageDelegate - + Encryption enabled Titkosítás bekapcsolva - + room name changed to: %1 a szoba neve megváltoztatva erre: %1 @@ -775,7 +788,7 @@ Példa: https://szerver.em:8787 szobanév eltávolítva - + topic changed to: %1 a téma megváltoztatva erre: %1 @@ -785,17 +798,17 @@ Példa: https://szerver.em:8787 téma eltávolítva - + %1 changed the room avatar - + %1 created and configured room: %2 %1 létrehozta és beállította a következő szobát: %2 - + %1 placed a voice call. %1 hanghívást kezdeményezett. @@ -810,23 +823,23 @@ Példa: https://szerver.em:8787 %1 hívást kezdeményezett. - + Negotiating call... Hívás előkészítése… - + %1 answered the call. %1 fogadta a hívást. - + removed eltávolítva - + %1 ended the call. %1 befejezte a hívást. @@ -834,7 +847,7 @@ Példa: https://szerver.em:8787 MessageInput - + Hang up Hívás befejezése @@ -855,6 +868,11 @@ Példa: https://szerver.em:8787 + Stickers + + + + Emoji Hangulatjelek @@ -872,17 +890,17 @@ Példa: https://szerver.em:8787 MessageView - + Edit Szerkesztés - + React Reakció - + Reply Válasz @@ -892,7 +910,7 @@ Példa: https://szerver.em:8787 Műveletek - + &Copy @@ -1100,7 +1118,7 @@ Példa: https://szerver.em:8787 Placeholder - + unimplemented event: nem implementált esemény: @@ -1220,7 +1238,7 @@ Példa: https://szerver.em:8787 ReplyPopup - + Close Bezárás @@ -1233,7 +1251,7 @@ Példa: https://szerver.em:8787 RoomInfo - + no version stored nincs tárolva verzió @@ -1241,7 +1259,7 @@ Példa: https://szerver.em:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Példa: https://szerver.em:8787 - - Accept - Elfogadás - - - - Decline - Elutasítás - - - + Status Message @@ -1341,20 +1349,41 @@ Példa: https://szerver.em:8787 Felhasználói beállítások + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings Szobabeállítások - + %1 member(s) %1 tag - + SETTINGS BEÁLLÍTÁSOK @@ -1438,11 +1467,6 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Room Version Szoba verziója - - - OK - OK - Failed to enable encryption: %1 @@ -1475,6 +1499,24 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Nem sikerült a kép feltöltése: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1532,7 +1574,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh StatusIndicator - + Failed Sikertelen @@ -1552,6 +1594,14 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Elolvasva + + StickerPicker + + + Search + Keresés + + Success @@ -1573,7 +1623,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 @@ -1584,7 +1634,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Nem sikerült titkosítani az eseményt, küldés megszakítva! - + Save image Kép mentése @@ -1717,12 +1767,12 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1801,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineRow - + Edited Szerkesztve @@ -1759,17 +1809,32 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineView - + No room open Nincs nyitott szoba - + %1 member(s) %1 tag - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Vissza a szobák listájára @@ -1777,7 +1842,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -1785,18 +1850,17 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TopBar - + Back to room list Vissza a szobák listájára - - + No room selected Nincs kiválasztva szoba - + Room options Szoba beállításai @@ -1837,7 +1901,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserProfile - + Global User Profile Globális felhasználói profil @@ -1847,7 +1911,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Szobai felhasználói profil - + Verify Hitelesítés @@ -1896,7 +1960,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserSettings - + Default Alapértelmezett @@ -1905,7 +1969,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh UserSettingsPage - + Minimize to tray Kicsinyítés a tálcára @@ -2363,7 +2427,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Waiting - + Waiting for other party… Várakozás a másik félre… @@ -2414,7 +2478,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő descriptiveTime - + Yesterday Tegnap @@ -2485,19 +2549,6 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő Nyisd meg a fallback-ket, kövesd az utasításokat, és erősítsd meg, ha végeztél velük! - - dialogs::InviteUsers - - - Cancel - Mégse - - - - User ID to invite - Meghívandó felhasználó azonosítója - - dialogs::JoinRoom diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index b0b8ec48..6056b31a 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation In attesa di conferma @@ -48,7 +48,7 @@ In attesa della conferma dall'altra parte per la verifica. - + Cancel Annulla @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Impossibile invitare l'utente: %1 @@ -157,12 +157,12 @@ - + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decifra i segreti @@ -426,12 +426,12 @@ EmojiPicker - + Search Cerca - + People Membri @@ -607,7 +607,7 @@ InputBar - + Select a file Seleziona un file @@ -617,17 +617,43 @@ Tutti i File (*) - + Failed to upload media. Please try again. Impossibile inviare il file multimediale. Per favore riprova. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + ID utente da invitare + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + - - Remove - Rimuovi + + Add + + + + + Invite + + + + + Cancel + Annulla @@ -744,23 +770,10 @@ Esempio: https://server.mio:8787 Accesso SSO fallito - - MemberList - - - Room members - Membri della stanza - - - - OK - OK - - MessageDelegate - + removed rimosso @@ -771,7 +784,7 @@ Esempio: https://server.mio:8787 Crittografia abilitata - + room name changed to: %1 nome della stanza cambiato in: %1 @@ -781,7 +794,7 @@ Esempio: https://server.mio:8787 nome della stanza rimosso - + topic changed to: %1 argomento cambiato in: %1 @@ -791,17 +804,17 @@ Esempio: https://server.mio:8787 argomento rimosso - + %1 changed the room avatar - + %1 created and configured room: %2 %1 creato e configurata stanza: %2 - + %1 placed a voice call. %1 ha avviato una chiamata audio. @@ -816,17 +829,17 @@ Esempio: https://server.mio:8787 - + %1 answered the call. %1 ha risposto alla chiamata. - + %1 ended the call. %1 ha terminato la chiamata. - + Negotiating call... @@ -834,7 +847,7 @@ Esempio: https://server.mio:8787 MessageInput - + Hang up Termina @@ -855,6 +868,11 @@ Esempio: https://server.mio:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Esempio: https://server.mio:8787 MessageView - + Edit Modifica - + React Reagisci - + Reply Risposta @@ -892,7 +910,7 @@ Esempio: https://server.mio:8787 Opzioni - + &Copy @@ -1101,7 +1119,7 @@ Verificare %1 adesso? Placeholder - + unimplemented event: evento non implementato: @@ -1221,7 +1239,7 @@ Verificare %1 adesso? ReplyPopup - + Close Chiudi @@ -1234,7 +1252,7 @@ Verificare %1 adesso? RoomInfo - + no version stored nessuna versione memorizzata @@ -1242,7 +1260,7 @@ Verificare %1 adesso? RoomList - + New tag @@ -1282,17 +1300,7 @@ Verificare %1 adesso? - - Accept - Accetta - - - - Decline - Rifiuta - - - + Status Message @@ -1342,20 +1350,42 @@ Verificare %1 adesso? Impostazioni utente + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1437,11 +1467,6 @@ Verificare %1 adesso? Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1474,6 +1499,24 @@ Verificare %1 adesso? Impossibile fare l'upload dell'immagine: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1531,7 +1574,7 @@ Verificare %1 adesso? StatusIndicator - + Failed Fallito @@ -1551,6 +1594,14 @@ Verificare %1 adesso? Letto + + StickerPicker + + + Search + Cerca + + Success @@ -1572,7 +1623,7 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 @@ -1583,7 +1634,7 @@ Verificare %1 adesso? - + Save image Salva immagine @@ -1717,12 +1768,12 @@ Verificare %1 adesso? %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1802,7 @@ Verificare %1 adesso? TimelineRow - + Edited @@ -1759,17 +1810,32 @@ Verificare %1 adesso? TimelineView - + No room open Nessuna stanza aperta - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1777,7 +1843,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1785,18 +1851,17 @@ Verificare %1 adesso? TopBar - + Back to room list - - + No room selected - + Room options Opzioni della stanza @@ -1837,7 +1902,7 @@ Verificare %1 adesso? UserProfile - + Global User Profile @@ -1847,7 +1912,7 @@ Verificare %1 adesso? - + Verify @@ -1896,7 +1961,7 @@ Verificare %1 adesso? UserSettings - + Default @@ -1905,7 +1970,7 @@ Verificare %1 adesso? UserSettingsPage - + Minimize to tray Minimizza nella tray @@ -2351,7 +2416,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2402,7 +2467,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2473,19 +2538,6 @@ This usually causes the application icon in the task bar to animate in some fash Apri il ripiego, segui i passaggi e conferma dopo averli completati. - - dialogs::InviteUsers - - - Cancel - Annulla - - - - User ID to invite - ID utente da invitare - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index c7872ce0..a8c18795 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel キャンセル @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ユーザーを招待できませんでした: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file ファイルを選択 @@ -617,17 +617,43 @@ 全てのファイル (*) - + Failed to upload media. Please try again. メディアをアップロードできませんでした。やり直して下さい。 - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + 招待するユーザーのID + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - 削除 + + Cancel + キャンセル @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - 部屋の参加者 - - - - OK - OK - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 暗号化が有効です - + room name changed to: %1 部屋名が変更されました: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 部屋名が削除されました - + topic changed to: %1 話題が変更されました: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 話題が削除されました - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji 絵文字 @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply 返信 @@ -888,7 +906,7 @@ Example: https://server.my:8787 オプション - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: 未実装のイベント: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close 閉じる @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - 容認 - - - - Decline - 拒否 - - - + Status Message @@ -1337,20 +1345,41 @@ Example: https://server.my:8787 ユーザー設定 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1461,6 @@ Example: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1469,6 +1493,24 @@ Example: https://server.my:8787 画像をアップロードできませんでした: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1568,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed 失敗 @@ -1546,6 +1588,14 @@ Example: https://server.my:8787 既読 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1617,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 @@ -1578,7 +1628,7 @@ Example: https://server.my:8787 - + Save image 画像を保存 @@ -1711,12 +1761,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1745,7 +1795,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1753,17 +1803,32 @@ Example: https://server.my:8787 TimelineView - + No room open 部屋が開いていません - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1771,7 +1836,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1779,18 +1844,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options 部屋のオプション @@ -1831,7 +1895,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1841,7 +1905,7 @@ Example: https://server.my:8787 - + Verify @@ -1890,7 +1954,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1899,7 +1963,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray トレイへ最小化 @@ -2345,7 +2409,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2396,7 +2460,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday 昨日 @@ -2467,19 +2531,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - キャンセル - - - - User ID to invite - 招待するユーザーのID - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index 0bdf3b63..aeb704eb 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation സ്ഥിരീകരണത്തിനായി കാത്തിരിക്കുന്നു @@ -48,7 +48,7 @@ - + Cancel റദ്ദാക്കു @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 ഉപയോക്താവിനെ ക്ഷണിക്കുന്നതിൽ പരാജയപ്പെട്ടു: %1 @@ -157,12 +157,12 @@ - + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search തിരയുക - + People ആളുകൾ @@ -607,7 +607,7 @@ InputBar - + Select a file ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക @@ -617,17 +617,43 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - നീക്കംചെയ്യുക + + Cancel + റദ്ദാക്കു @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - ശരി - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed നീക്കംചെയ്‌തു - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji ഇമോജി @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close അടയ്‌ക്കുക @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - നിരസിക്കുക - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - ശരി - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + തിരയുക + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - റദ്ദാക്കു - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index 3f2a147f..ba3ceec1 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel Annuleren @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Gebruiker uitnodigen mislukt: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file Kies een bestand @@ -617,18 +617,44 @@ Alle bestanden (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Uit te nodigen gebruikers-id + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove + + Invite + + + Cancel + Annuleren + LoginPage @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - Kamerleden - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Accepteren - - - - Decline - Afwijzen - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image Afbeelding opslaan @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Je bent lid geworden van deze kamer. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimaliseren naar systeemvak @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Annuleren - - - - User ID to invite - Uit te nodigen gebruikers-id - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 6abcd147..4b6c31f2 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Oczekiwanie na potwierdzenie @@ -48,7 +48,7 @@ Oczekiwanie na dokończenie weryfikacji przez drugą stronę. - + Cancel Anuluj @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nie udało się zaprosić użytkownika: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search Szukaj - + People Ludzie @@ -607,7 +607,7 @@ InputBar - + Select a file Wybierz plik @@ -617,17 +617,43 @@ Wszystkie pliki (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - Usuń + + Invite users to %1 + + + + + User ID to invite + ID użytkownika do zaproszenia + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + Anuluj @@ -742,23 +768,10 @@ Example: https://server.my:8787 Logowanie SSO zakończone niepowodzeniem - - MemberList - - - Room members - Członkowie pokoju - - - - OK - OK - - MessageDelegate - + removed @@ -769,7 +782,7 @@ Example: https://server.my:8787 Szyfrowanie włączone - + room name changed to: %1 Nazwa pokoju zmieniona na: %1 @@ -779,7 +792,7 @@ Example: https://server.my:8787 usunięto nazwę pokoju - + topic changed to: %1 temat zmieniono na: %1 @@ -789,17 +802,17 @@ Example: https://server.my:8787 usunięto temat - + %1 changed the room avatar - + %1 created and configured room: %2 %1 utworzył i skonfigurował pokój: %2 - + %1 placed a voice call. %1 rozpoczął(-ęła) połączenie głosowe. @@ -814,17 +827,17 @@ Example: https://server.my:8787 %1 rozpoczął(-ęła) połączenie. - + %1 answered the call. %1 odebrał(a) połączenie. - + %1 ended the call. %1 zakończył(a) połączenie. - + Negotiating call... Negocjowanie połączenia… @@ -832,7 +845,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -853,6 +866,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -870,17 +888,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -890,7 +908,7 @@ Example: https://server.my:8787 - + &Copy @@ -1098,7 +1116,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: Niezaimplementowane wydarzenie: @@ -1218,7 +1236,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Zamknij @@ -1231,7 +1249,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1239,7 +1257,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1279,17 +1297,7 @@ Example: https://server.my:8787 - - Accept - Akceptuj - - - - Decline - Odrzuć - - - + Status Message @@ -1339,20 +1347,43 @@ Example: https://server.my:8787 Ustawienia użytkownika + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1434,11 +1465,6 @@ Example: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1471,6 +1497,24 @@ Example: https://server.my:8787 Nie udało się wysłać obrazu: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1528,7 +1572,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1548,6 +1592,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + Szukaj + + Success @@ -1569,7 +1621,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 @@ -1580,7 +1632,7 @@ Example: https://server.my:8787 - + Save image Zapisz obraz @@ -1715,12 +1767,12 @@ Example: https://server.my:8787 - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. @@ -1749,7 +1801,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1757,17 +1809,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1775,7 +1842,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1783,18 +1850,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options Ustawienia pokoju @@ -1835,7 +1901,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1845,7 +1911,7 @@ Example: https://server.my:8787 - + Verify @@ -1894,7 +1960,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1903,7 +1969,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Zminimalizuj do paska zadań @@ -2349,7 +2415,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2400,7 +2466,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2471,19 +2537,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Anuluj - - - - User ID to invite - ID użytkownika do zaproszenia - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index fa0ea193..7774ba58 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ Esperando o outro lado completar a verificação. - + Cancel Cancelar @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Falha ao convidar usuário: %1 @@ -157,12 +157,12 @@ - + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Migração do cache falhou! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,18 +617,44 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove + + Invite + + + Cancel + Cancelar + LoginPage @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - Aceitar - - - - Decline - Rejeitar - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - Cancelar - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index 81343f97..cafbcdd6 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel @@ -740,28 +766,15 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + Encryption enabled - + room name changed to: %1 @@ -771,7 +784,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -781,17 +794,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -806,23 +819,23 @@ Example: https://server.my:8787 - + Negotiating call... - + %1 answered the call. - + removed - + %1 ended the call. @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index c21bb069..8ff28d24 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Nu s-a putut invita utilizatorul: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,17 +617,43 @@ Toate fișierele (*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + IDul utilizatorului de invitat + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - Ștergere + + Cancel + @@ -744,23 +770,10 @@ Exemplu: https://serverul.meu:8787 Conectarea SSO a eșuat - - MemberList - - - Room members - Membrii camerei - - - - OK - OK - - MessageDelegate - + removed @@ -771,7 +784,7 @@ Exemplu: https://serverul.meu:8787 Criptare activată - + room name changed to: %1 numele camerei schimbat la: %1 @@ -781,7 +794,7 @@ Exemplu: https://serverul.meu:8787 numele camerei șters - + topic changed to: %1 subiect schimbat la: %1 @@ -791,17 +804,17 @@ Exemplu: https://serverul.meu:8787 subiect șters - + %1 changed the room avatar - + %1 created and configured room: %2 %1 a creat și configurat camera: %2 - + %1 placed a voice call. @@ -816,17 +829,17 @@ Exemplu: https://serverul.meu:8787 - + %1 answered the call. %1 a răspuns apelului. - + %1 ended the call. %1 a închis apelul. - + Negotiating call... @@ -834,7 +847,7 @@ Exemplu: https://serverul.meu:8787 MessageInput - + Hang up @@ -855,6 +868,11 @@ Exemplu: https://serverul.meu:8787 + Stickers + + + + Emoji @@ -872,17 +890,17 @@ Exemplu: https://serverul.meu:8787 MessageView - + Edit - + React - + Reply Răspuns @@ -892,7 +910,7 @@ Exemplu: https://serverul.meu:8787 Opțiuni - + &Copy @@ -1100,7 +1118,7 @@ Exemplu: https://serverul.meu:8787 Placeholder - + unimplemented event: eveniment neimplementat: @@ -1220,7 +1238,7 @@ Exemplu: https://serverul.meu:8787 ReplyPopup - + Close Închide @@ -1233,7 +1251,7 @@ Exemplu: https://serverul.meu:8787 RoomInfo - + no version stored nicio versiune stocată @@ -1241,7 +1259,7 @@ Exemplu: https://serverul.meu:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exemplu: https://serverul.meu:8787 - - Accept - Acceptare - - - - Decline - Refuzare - - - + Status Message @@ -1341,20 +1349,43 @@ Exemplu: https://serverul.meu:8787 Setări utilizator + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1436,11 +1467,6 @@ Exemplu: https://serverul.meu:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1499,24 @@ Exemplu: https://serverul.meu:8787 Nu s-a putut încărca imaginea: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1574,7 @@ Exemplu: https://serverul.meu:8787 StatusIndicator - + Failed Eșuat @@ -1550,6 +1594,14 @@ Exemplu: https://serverul.meu:8787 Citit + + StickerPicker + + + Search + + + Success @@ -1571,7 +1623,7 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 @@ -1582,7 +1634,7 @@ Exemplu: https://serverul.meu:8787 - + Save image Salvați imaginea @@ -1717,12 +1769,12 @@ Exemplu: https://serverul.meu:8787 %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1803,7 @@ Exemplu: https://serverul.meu:8787 TimelineRow - + Edited @@ -1759,17 +1811,32 @@ Exemplu: https://serverul.meu:8787 TimelineView - + No room open Nicio cameră deschisă - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1777,7 +1844,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1785,18 +1852,17 @@ Exemplu: https://serverul.meu:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1837,7 +1903,7 @@ Exemplu: https://serverul.meu:8787 UserProfile - + Global User Profile @@ -1847,7 +1913,7 @@ Exemplu: https://serverul.meu:8787 - + Verify @@ -1896,7 +1962,7 @@ Exemplu: https://serverul.meu:8787 UserSettings - + Default @@ -1905,7 +1971,7 @@ Exemplu: https://serverul.meu:8787 UserSettingsPage - + Minimize to tray Minimizează în bara de notificări @@ -2351,7 +2417,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2402,7 +2468,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2473,19 +2539,6 @@ This usually causes the application icon in the task bar to animate in some fash Deschideți fallback, urmăriți pașii și confirmați după ce i-ați completat. - - dialogs::InviteUsers - - - Cancel - Anulare - - - - User ID to invite - IDul utilizatorului de invitat - - dialogs::JoinRoom diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index 6f2b19af..be97413f 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Ожидание Подтверждения @@ -48,7 +48,7 @@ Ожидание подтверждения у собеседника. - + Cancel Отмена @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Не удалось пригласить пользователя: %1 @@ -157,12 +157,12 @@ - + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Расшифровать секреты @@ -426,12 +426,12 @@ EmojiPicker - + Search Поиск - + People Люди @@ -607,7 +607,7 @@ InputBar - + Select a file Выберите файл @@ -617,17 +617,43 @@ Все файлы (*) - + Failed to upload media. Please try again. Не удалось загрузить медиа. Пожалуйста попробуйте ещё раз - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Идентификатор пользователя + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + - - Remove - Удалить + + Add + + + + + Invite + + + + + Cancel + @@ -744,23 +770,10 @@ Example: https://server.my:8787 SSO вход не удался - - MemberList - - - Room members - Участники комнаты - - - - OK - ОК - - MessageDelegate - + removed убрано @@ -771,7 +784,7 @@ Example: https://server.my:8787 Шифрование включено - + room name changed to: %1 имя комнаты изменено на: %1 @@ -781,7 +794,7 @@ Example: https://server.my:8787 название комнаты убрано - + topic changed to: %1 тема изменена на: %1 @@ -791,17 +804,17 @@ Example: https://server.my:8787 тема убрана - + %1 changed the room avatar - + %1 created and configured room: %2 %1 создал и настроил комнату: %2 - + %1 placed a voice call. %1 начал голосовой звонок. @@ -816,17 +829,17 @@ Example: https://server.my:8787 %1 начал вызов. - + %1 answered the call. %1 ответил на звонок. - + %1 ended the call. %1 завершил вызов. - + Negotiating call... Совершение звонка... @@ -834,7 +847,7 @@ Example: https://server.my:8787 MessageInput - + Hang up Завершить звонок @@ -855,6 +868,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji Эмоджи @@ -872,17 +890,17 @@ Example: https://server.my:8787 MessageView - + Edit Редактировать - + React Реакция - + Reply Ответить @@ -892,7 +910,7 @@ Example: https://server.my:8787 Опции - + &Copy @@ -1100,7 +1118,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: не реализованное событие @@ -1220,7 +1238,7 @@ Example: https://server.my:8787 ReplyPopup - + Close Закрыть @@ -1233,7 +1251,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored нет сохраненной версии @@ -1241,7 +1259,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Example: https://server.my:8787 - - Accept - Принять - - - - Decline - Отказаться - - - + Status Message @@ -1341,20 +1349,43 @@ Example: https://server.my:8787 Пользовательские настройки + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + + Invite more people + + + RoomSettings - + Room Settings Настройки комнаты - + %1 member(s) %1 участник(ов) - + SETTINGS НАЙСТРОЙКИ @@ -1436,11 +1467,6 @@ Example: https://server.my:8787 Room Version Версия Комнаты - - - OK - ОК - Failed to enable encryption: %1 @@ -1473,6 +1499,24 @@ Example: https://server.my:8787 Не удалось загрузить изображение: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1574,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed Не удалоcь @@ -1550,6 +1594,14 @@ Example: https://server.my:8787 Прочитано + + StickerPicker + + + Search + Поиск + + Success @@ -1571,7 +1623,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 @@ -1582,7 +1634,7 @@ Example: https://server.my:8787 Не удалось зашифровать сообщение, отправка отменена! - + Save image Сохранить изображение @@ -1717,12 +1769,12 @@ Example: https://server.my:8787 %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. @@ -1751,7 +1803,7 @@ Example: https://server.my:8787 TimelineRow - + Edited Изменено @@ -1759,17 +1811,32 @@ Example: https://server.my:8787 TimelineView - + No room open Комната не выбрана - + %1 member(s) %1 участник(ов) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Вернуться к списку комнат @@ -1777,7 +1844,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -1785,18 +1852,17 @@ Example: https://server.my:8787 TopBar - + Back to room list Вернуться к списку комнат - - + No room selected Комнаты не выбраны - + Room options Настройки комнаты @@ -1837,7 +1903,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile Глобальный Пользовательский Профиль @@ -1847,7 +1913,7 @@ Example: https://server.my:8787 Поользовательский Профиль в Комнате - + Verify Верифицировать @@ -1896,7 +1962,7 @@ Example: https://server.my:8787 UserSettings - + Default По умолчанию @@ -1905,7 +1971,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray Сворачивать в системную панель @@ -2357,7 +2423,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2408,7 +2474,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Вчера @@ -2479,19 +2545,6 @@ This usually causes the application icon in the task bar to animate in some fash Запустите резервный вариант, пройдите его шаги и подтвердите завершение. - - dialogs::InviteUsers - - - Cancel - Отмена - - - - User ID to invite - Идентификатор пользователя - - dialogs::JoinRoom diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index a80adb1b..67f09710 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file @@ -617,16 +617,42 @@ - + Failed to upload media. Please try again. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + - - Remove + + Invite + + + + + Cancel @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - - - - - Decline - - - - + Status Message @@ -1337,20 +1345,42 @@ Example: https://server.my:8787 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1462,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1494,24 @@ Example: https://server.my:8787 + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1569,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1589,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1618,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1578,7 +1629,7 @@ Example: https://server.my:8787 - + Save image @@ -1712,12 +1763,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1746,7 +1797,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1754,17 +1805,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1772,7 +1838,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1780,18 +1846,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options @@ -1832,7 +1897,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1842,7 +1907,7 @@ Example: https://server.my:8787 - + Verify @@ -1891,7 +1956,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1900,7 +1965,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray @@ -2346,7 +2411,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2397,7 +2462,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2468,19 +2533,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - - - - - User ID to invite - - - dialogs::JoinRoom diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index 8069dcea..fb662292 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation Inväntar Bekräftelse @@ -48,7 +48,7 @@ Väntar på att motparten ska slutföra verifikationen. - + Cancel Avbryt @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 Kunde inte bjuda in användare: %1 @@ -157,12 +157,12 @@ - + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekryptera hemliga nycklar @@ -426,12 +426,12 @@ EmojiPicker - + Search Sök - + People Personer @@ -607,7 +607,7 @@ InputBar - + Select a file Välj en fil @@ -617,17 +617,43 @@ Alla Filer (*) - + Failed to upload media. Please try again. Kunde inte ladda upp media. Vänligen försök igen. - InviteeItem + InviteDialog + + + Invite users to %1 + + + + + User ID to invite + Användar-ID att bjuda in + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + - - Remove - Ta bort + + Cancel + Avbryt @@ -744,28 +770,15 @@ Exempel: https://server.my:8787 SSO-inloggning misslyckades - - MemberList - - - Room members - Rumsmedlemmar - - - - OK - OK - - MessageDelegate - + Encryption enabled Kryptering aktiverad - + room name changed to: %1 rummets namn ändrat till: %1 @@ -775,7 +788,7 @@ Exempel: https://server.my:8787 tog bort rummets namn - + topic changed to: %1 ämne ändrat till: %1 @@ -785,17 +798,17 @@ Exempel: https://server.my:8787 tog bort ämne - + %1 changed the room avatar - + %1 created and configured room: %2 %1 skapade och konfigurerade rum: %2 - + %1 placed a voice call. %1 påbörjade ett röstsamtal. @@ -810,23 +823,23 @@ Exempel: https://server.my:8787 %1 påbörjade ett samtal. - + Negotiating call... Förhandlar samtal… - + %1 answered the call. %1 besvarade samtalet. - + removed borttagen - + %1 ended the call. %1 avslutade samtalet. @@ -834,7 +847,7 @@ Exempel: https://server.my:8787 MessageInput - + Hang up Lägg på @@ -855,6 +868,11 @@ Exempel: https://server.my:8787 + Stickers + + + + Emoji Emoji @@ -872,17 +890,17 @@ Exempel: https://server.my:8787 MessageView - + Edit - + React Reagera - + Reply Svara @@ -892,7 +910,7 @@ Exempel: https://server.my:8787 Alternativ - + &Copy @@ -1100,7 +1118,7 @@ Exempel: https://server.my:8787 Placeholder - + unimplemented event: ej implementerat event: @@ -1220,7 +1238,7 @@ Exempel: https://server.my:8787 ReplyPopup - + Close Stäng @@ -1233,7 +1251,7 @@ Exempel: https://server.my:8787 RoomInfo - + no version stored ingen version lagrad @@ -1241,7 +1259,7 @@ Exempel: https://server.my:8787 RoomList - + New tag @@ -1281,17 +1299,7 @@ Exempel: https://server.my:8787 - - Accept - Godkänn - - - - Decline - - - - + Status Message @@ -1341,20 +1349,42 @@ Exempel: https://server.my:8787 Användarinställningar + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1436,11 +1466,6 @@ Exempel: https://server.my:8787 Room Version - - - OK - OK - Failed to enable encryption: %1 @@ -1473,6 +1498,24 @@ Exempel: https://server.my:8787 Kunde inte ladda upp bilden: %s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1530,7 +1573,7 @@ Exempel: https://server.my:8787 StatusIndicator - + Failed Misslyckat @@ -1550,6 +1593,14 @@ Exempel: https://server.my:8787 Läst + + StickerPicker + + + Search + Sök + + Success @@ -1571,7 +1622,7 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 @@ -1582,7 +1633,7 @@ Exempel: https://server.my:8787 Kunde inte kryptera event, sändning avbruten! - + Save image Spara bild @@ -1716,12 +1767,12 @@ Exempel: https://server.my:8787 %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. @@ -1750,7 +1801,7 @@ Exempel: https://server.my:8787 TimelineRow - + Edited @@ -1758,17 +1809,32 @@ Exempel: https://server.my:8787 TimelineView - + No room open Inget rum öppet - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list Tillbaka till rumlista @@ -1776,7 +1842,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -1784,18 +1850,17 @@ Exempel: https://server.my:8787 TopBar - + Back to room list Tillbaka till rumlista - - + No room selected Inget rum markerat - + Room options Alternativ för rum @@ -1836,7 +1901,7 @@ Exempel: https://server.my:8787 UserProfile - + Global User Profile @@ -1846,7 +1911,7 @@ Exempel: https://server.my:8787 - + Verify Bekräfta @@ -1895,7 +1960,7 @@ Exempel: https://server.my:8787 UserSettings - + Default @@ -1904,7 +1969,7 @@ Exempel: https://server.my:8787 UserSettingsPage - + Minimize to tray Minimera till systemtråg @@ -2358,7 +2423,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Waiting - + Waiting for other party… Väntar på motparten… @@ -2409,7 +2474,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< descriptiveTime - + Yesterday Igår @@ -2480,19 +2545,6 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< Öppna reserven, följ stegen och bekräfta när du slutfört dem. - - dialogs::InviteUsers - - - Cancel - Avbryt - - - - User ID to invite - Användar-ID att bjuda in - - dialogs::JoinRoom diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index d468dfaa..c84e3c82 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -38,7 +38,7 @@ AwaitingVerificationConfirmation - + Awaiting Confirmation @@ -48,7 +48,7 @@ - + Cancel 取消 @@ -125,7 +125,7 @@ ChatPage - + Failed to invite user: %1 邀请用户失败: %1 @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -426,12 +426,12 @@ EmojiPicker - + Search - + People @@ -607,7 +607,7 @@ InputBar - + Select a file 选择一个文件 @@ -617,17 +617,43 @@ 所有文件(*) - + Failed to upload media. Please try again. - InviteeItem + InviteDialog - - Remove - 移除 + + Invite users to %1 + + + + + User ID to invite + 要邀请的用户 ID + + + + @joe:matrix.org + Example user id. The name 'joe' can be localized however you want. + + + + + Add + + + + + Invite + + + + + Cancel + 取消 @@ -740,23 +766,10 @@ Example: https://server.my:8787 - - MemberList - - - Room members - 聊天室成员 - - - - OK - - - MessageDelegate - + removed @@ -767,7 +780,7 @@ Example: https://server.my:8787 - + room name changed to: %1 @@ -777,7 +790,7 @@ Example: https://server.my:8787 - + topic changed to: %1 @@ -787,17 +800,17 @@ Example: https://server.my:8787 - + %1 changed the room avatar - + %1 created and configured room: %2 - + %1 placed a voice call. @@ -812,17 +825,17 @@ Example: https://server.my:8787 - + %1 answered the call. - + %1 ended the call. - + Negotiating call... @@ -830,7 +843,7 @@ Example: https://server.my:8787 MessageInput - + Hang up @@ -851,6 +864,11 @@ Example: https://server.my:8787 + Stickers + + + + Emoji @@ -868,17 +886,17 @@ Example: https://server.my:8787 MessageView - + Edit - + React - + Reply @@ -888,7 +906,7 @@ Example: https://server.my:8787 - + &Copy @@ -1096,7 +1114,7 @@ Example: https://server.my:8787 Placeholder - + unimplemented event: @@ -1216,7 +1234,7 @@ Example: https://server.my:8787 ReplyPopup - + Close @@ -1229,7 +1247,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1237,7 +1255,7 @@ Example: https://server.my:8787 RoomList - + New tag @@ -1277,17 +1295,7 @@ Example: https://server.my:8787 - - Accept - 接受 - - - - Decline - 拒绝 - - - + Status Message @@ -1337,20 +1345,41 @@ Example: https://server.my:8787 用户设置 + + RoomMembers + + + Members of %1 + + + + + %n people in %1 + Summary above list of members + + + + + + + Invite more people + + + RoomSettings - + Room Settings - + %1 member(s) - + SETTINGS @@ -1432,11 +1461,6 @@ Example: https://server.my:8787 Room Version - - - OK - - Failed to enable encryption: %1 @@ -1469,6 +1493,24 @@ Example: https://server.my:8787 上传图像失败:%s + + RoomlistModel + + + Pending invite. + + + + + Previewing this room + + + + + No preview available + + + ScreenShare @@ -1526,7 +1568,7 @@ Example: https://server.my:8787 StatusIndicator - + Failed @@ -1546,6 +1588,14 @@ Example: https://server.my:8787 + + StickerPicker + + + Search + + + Success @@ -1567,7 +1617,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 @@ -1578,7 +1628,7 @@ Example: https://server.my:8787 - + Save image 保存图像 @@ -1711,12 +1761,12 @@ Example: https://server.my:8787 - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. @@ -1745,7 +1795,7 @@ Example: https://server.my:8787 TimelineRow - + Edited @@ -1753,17 +1803,32 @@ Example: https://server.my:8787 TimelineView - + No room open - + %1 member(s) - + + join the conversation + + + + + accept invite + + + + + decline invite + + + + Back to room list @@ -1771,7 +1836,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -1779,18 +1844,17 @@ Example: https://server.my:8787 TopBar - + Back to room list - - + No room selected - + Room options 聊天室选项 @@ -1831,7 +1895,7 @@ Example: https://server.my:8787 UserProfile - + Global User Profile @@ -1841,7 +1905,7 @@ Example: https://server.my:8787 - + Verify @@ -1890,7 +1954,7 @@ Example: https://server.my:8787 UserSettings - + Default @@ -1899,7 +1963,7 @@ Example: https://server.my:8787 UserSettingsPage - + Minimize to tray 最小化至托盘 @@ -2345,7 +2409,7 @@ This usually causes the application icon in the task bar to animate in some fash Waiting - + Waiting for other party… @@ -2396,7 +2460,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2467,19 +2531,6 @@ This usually causes the application icon in the task bar to animate in some fash - - dialogs::InviteUsers - - - Cancel - 取消 - - - - User ID to invite - 要邀请的用户 ID - - dialogs::JoinRoom diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index c6b42d29..5aaf0d6d 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -29,7 +29,7 @@ ApplicationWindow { close(); } - title: qsTr("Invite users to ") + plainRoomName + title: qsTr("Invite users to %1").arg(plainRoomName) x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 3758cb0b..bc8a90d2 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -14,7 +14,7 @@ ApplicationWindow { property MemberList members - title: qsTr("Members of ") + members.roomName + title: qsTr("Members of %1").arg(members.roomName) x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 650 @@ -46,7 +46,7 @@ ApplicationWindow { ElidedLabel { font.pixelSize: fontMetrics.font.pixelSize * 2 - fullText: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + members.roomName + fullText: qsTr("%n people in %1", "Summary above list of members", members.memberCount).arg(members.roomName) Layout.alignment: Qt.AlignHCenter elideWidth: parent.width - Nheko.paddingMedium } -- cgit 1.5.1 From 44be4c1f4a4e1ef60fbb6c1f51adc93e5a555f14 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 21 Jul 2021 18:56:20 -0400 Subject: Move various room auxiliary functions to TimelineManager --- resources/qml/RoomMembers.qml | 5 ++--- resources/qml/RoomSettings.qml | 2 +- resources/qml/Root.qml | 2 +- resources/qml/TopBar.qml | 10 +++++----- src/timeline/TimelineModel.cpp | 25 ------------------------- src/timeline/TimelineModel.h | 7 ------- src/timeline/TimelineViewManager.cpp | 28 ++++++++++++++++++++++++++++ src/timeline/TimelineViewManager.h | 7 +++++++ 8 files changed, 44 insertions(+), 42 deletions(-) diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 3758cb0b..5bf847ca 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -41,7 +41,7 @@ ApplicationWindow { displayName: members.roomName Layout.alignment: Qt.AlignHCenter url: members.avatarUrl.replace("mxc://", "image://MxcImage/") - onClicked: Rooms.currentRoom.openRoomSettings(members.roomId) + onClicked: TimelineManager.openRoomSettings(members.roomId) } ElidedLabel { @@ -57,7 +57,7 @@ ApplicationWindow { hoverEnabled: true ToolTip.visible: hovered ToolTip.text: qsTr("Invite more people") - onClicked: Rooms.currentRoom.openInviteUsers() + onClicked: TimelineManager.openInviteUsers(members.roomId) } ScrollView { @@ -121,7 +121,6 @@ ApplicationWindow { footer: Item { width: parent.width visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers - // use the default height if it's visible, otherwise no height at all height: membersLoadingSpinner.height anchors.margins: Nheko.paddingMedium diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 8746d4d3..b4936f3e 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -107,7 +107,7 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter TapHandler { - onTapped: Rooms.currentRoom.openRoomMembers(roomSettings.roomId) + onTapped: TimelineManager.openRoomMembers(roomSettings.roomId) } CursorShape { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index f71c18e2..8e226639 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -133,7 +133,7 @@ Page { } Connections { - target: Rooms.currentRoom + target: TimelineManager onOpenRoomMembersDialog: { var membersDialog = roomMembersComponent.createObject(timelineRoot, { "members": members, diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 48491f84..8543d02a 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -24,7 +24,7 @@ Rectangle { TapHandler { onSingleTapped: { if (room) - room.openRoomSettings(); + TimelineManager.openRoomSettings(room.roomId); eventPoint.accepted = true; } @@ -66,7 +66,7 @@ Rectangle { displayName: roomName onClicked: { if (room) - room.openRoomSettings(); + TimelineManager.openRoomSettings(room.roomId); } } @@ -111,12 +111,12 @@ Rectangle { Platform.MenuItem { visible: room ? room.permissions.canInvite() : false text: qsTr("Invite users") - onTriggered: Rooms.currentRoom.openInviteUsers() + onTriggered: TimelineManager.openInviteUsers(room.roomId) } Platform.MenuItem { text: qsTr("Members") - onTriggered: Rooms.currentRoom.openRoomMembers() + onTriggered: TimelineManager.openRoomMembers(room.roomId) } Platform.MenuItem { @@ -126,7 +126,7 @@ Rectangle { Platform.MenuItem { text: qsTr("Settings") - onTriggered: room.openRoomSettings() + onTriggered: TimelineManager.openRoomSettings(room.roomId) } } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index e9fa4a05..ee5564a5 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1066,31 +1066,6 @@ TimelineModel::openUserProfile(QString userid) emit manager_->openProfile(userProfile); } -void -TimelineModel::openRoomMembers(QString room_id) -{ - MemberList *memberList = new MemberList(room_id == QString() ? roomId() : room_id, this); - emit openRoomMembersDialog(memberList); -} - -void -TimelineModel::openRoomSettings(QString room_id) -{ - RoomSettings *settings = new RoomSettings(room_id == QString() ? roomId() : room_id, this); - connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); - emit openRoomSettingsDialog(settings); -} - -void -TimelineModel::openInviteUsers(QString roomId) -{ - InviteesModel *model = new InviteesModel{this}; - connect(model, &InviteesModel::accept, this, [this, model, roomId]() { - emit manager_->inviteUsers(roomId == QString() ? room_id_ : roomId, model->mxids()); - }); - emit openInviteUsersDialog(model); -} - void TimelineModel::replyAction(QString id) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 077245cb..0e2ce153 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -239,9 +239,6 @@ public: Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid); - Q_INVOKABLE void openRoomMembers(QString room_id = QString()); - Q_INVOKABLE void openRoomSettings(QString room_id = QString()); - Q_INVOKABLE void openInviteUsers(QString roomId = QString()); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; @@ -358,10 +355,6 @@ signals: void lastMessageChanged(); void notificationsChanged(); - void openRoomMembersDialog(MemberList *members); - void openRoomSettingsDialog(RoomSettings *settings); - void openInviteUsersDialog(InviteesModel *invitees); - void newMessageToSend(mtx::events::collections::TimelineEvents event); void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); void updateFlowEventId(std::string event_id); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 64493e5b..b1643798 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -351,6 +351,34 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par &TimelineViewManager::openImageOverlayInternal); } +void +TimelineViewManager::openRoomMembers(QString room_id) +{ + MemberList *memberList = new MemberList(room_id, this); + emit openRoomMembersDialog(memberList); +} + +void +TimelineViewManager::openRoomSettings(QString room_id) +{ + RoomSettings *settings = new RoomSettings(room_id, this); + connect(rooms_->getRoomById(room_id).data(), + &TimelineModel::roomAvatarUrlChanged, + settings, + &RoomSettings::avatarChanged); + emit openRoomSettingsDialog(settings); +} + +void +TimelineViewManager::openInviteUsers(QString roomId) +{ + InviteesModel *model = new InviteesModel{this}; + connect(model, &InviteesModel::accept, this, [this, model, roomId]() { + emit inviteUsers(roomId, model->mxids()); + }); + emit openInviteUsersDialog(model); +} + void TimelineViewManager::setVideoCallItem() { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 945ba2d5..374685e3 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -64,6 +64,10 @@ public: Q_INVOKABLE QString userPresence(QString id) const; Q_INVOKABLE QString userStatus(QString id) const; + Q_INVOKABLE void openRoomMembers(QString room_id); + Q_INVOKABLE void openRoomSettings(QString room_id); + Q_INVOKABLE void openInviteUsers(QString roomId); + Q_INVOKABLE void focusMessageInput(); Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); @@ -85,6 +89,9 @@ signals: void focusChanged(); void focusInput(); void openImageOverlayInternalCb(QString eventId, QImage img); + void openRoomMembersDialog(MemberList *members); + void openRoomSettingsDialog(RoomSettings *settings); + void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); public slots: -- cgit 1.5.1 From 0971fd0fccddf1aa8c88780961df12803a132aa6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 21 Jul 2021 20:37:36 -0400 Subject: Pad the loading spinner --- resources/qml/MessageView.qml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 564c8dc7..d2c97d57 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -477,12 +477,22 @@ ScrollView { } - footer: Spinner { + footer: Item { anchors.horizontalCenter: parent.horizontalCenter - running: chat.model && chat.model.paginationInProgress - foreground: Nheko.colors.mid + anchors.margins: Nheko.paddingLarge visible: chat.model && chat.model.paginationInProgress - z: 3 + // hacky, but works + height: loadingSpinner.height + 2 * Nheko.paddingLarge + + Spinner { + id: loadingSpinner + + anchors.centerIn: parent + anchors.margins: Nheko.paddingLarge + running: chat.model && chat.model.paginationInProgress + foreground: Nheko.colors.mid + z: 3 + } } } -- cgit 1.5.1 From bbecadf1a8c3d78609a6112a879288f841b592f4 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 21 Jul 2021 20:38:18 -0400 Subject: Add functionality for loading global user profile where needed This is so viewing profiles from the invite dialog will work as expected. --- resources/qml/InviteDialog.qml | 2 +- src/timeline/TimelineViewManager.cpp | 6 ++++++ src/timeline/TimelineViewManager.h | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 5aaf0d6d..50287ad5 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -107,7 +107,7 @@ ApplicationWindow { userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName - onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + onClicked: TimelineManager.openGlobalUserProfile(model.mxid) } ColumnLayout { diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b1643798..da3ba282 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -379,6 +379,12 @@ TimelineViewManager::openInviteUsers(QString roomId) emit openInviteUsersDialog(model); } +void TimelineViewManager::openGlobalUserProfile(QString userId) +{ + UserProfile *profile = new UserProfile{QString{}, userId, this}; + emit openProfile(profile); +} + void TimelineViewManager::setVideoCallItem() { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 374685e3..bfc116b1 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -67,6 +67,7 @@ public: Q_INVOKABLE void openRoomMembers(QString room_id); Q_INVOKABLE void openRoomSettings(QString room_id); Q_INVOKABLE void openInviteUsers(QString roomId); + Q_INVOKABLE void openGlobalUserProfile(QString userId); Q_INVOKABLE void focusMessageInput(); Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; -- cgit 1.5.1 From a2ec4a61c58dde172f17011184581e9fed3fffb5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Jul 2021 11:15:29 +0200 Subject: Add suggested remote names to flatpak files --- nheko-nightly.flatpakref | 1 + nheko-nightly.flatpakrepo | 1 + 2 files changed, 2 insertions(+) diff --git a/nheko-nightly.flatpakref b/nheko-nightly.flatpakref index 7d27bdfe..74e47ecd 100644 --- a/nheko-nightly.flatpakref +++ b/nheko-nightly.flatpakref @@ -3,6 +3,7 @@ Title=Nheko Nightly Name=io.github.NhekoReborn.Nheko Branch=master Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko-reborn.github.io/ Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo diff --git a/nheko-nightly.flatpakrepo b/nheko-nightly.flatpakrepo index 4fb1bc55..680558af 100644 --- a/nheko-nightly.flatpakrepo +++ b/nheko-nightly.flatpakrepo @@ -1,6 +1,7 @@ [Flatpak Repo] Title=Nheko Nightly Url=https://flatpak.neko.dev/repo/nightly +SuggestRemoteName=nheko-nightlies Homepage=https://nheko.im/ Comment=Nheko nightly release repository Description=Nheko nightly release repository -- cgit 1.5.1 From 5ae73bf8fd65c9d5897b7b87bb41513dc37171a3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Jul 2021 12:00:14 +0200 Subject: Add trailing newline to session export to make gomuks happy --- src/UserSettingsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index ffaebe61..a062780a 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -1399,7 +1399,7 @@ UserSettingsPage::exportSessionKeys() QString suffix("-----END MEGOLM SESSION DATA-----"); QString newline("\n"); QTextStream out(&file); - out << prefix << newline << b64 << newline << suffix; + out << prefix << newline << b64 << newline << suffix << newline; file.close(); } catch (const std::exception &e) { QMessageBox::warning(this, tr("Error"), e.what()); -- cgit 1.5.1 From 0ce7d02abed2f687a1a9795da8ab8e2ec96b0d65 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 22 Jul 2021 07:55:12 -0400 Subject: make lint --- resources/qml/MessageView.qml | 1 + src/timeline/TimelineViewManager.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index d2c97d57..50cbd371 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -493,6 +493,7 @@ ScrollView { foreground: Nheko.colors.mid z: 3 } + } } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index da3ba282..c08cfd53 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -379,7 +379,8 @@ TimelineViewManager::openInviteUsers(QString roomId) emit openInviteUsersDialog(model); } -void TimelineViewManager::openGlobalUserProfile(QString userId) +void +TimelineViewManager::openGlobalUserProfile(QString userId) { UserProfile *profile = new UserProfile{QString{}, userId, this}; emit openProfile(profile); -- cgit 1.5.1 From 0ac550ecbb77c8aa3e3427c466f1be2c436a42aa Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Jul 2021 15:07:33 +0200 Subject: Show confirmation dialog when leaving a room via the context menu --- resources/qml/RoomList.qml | 12 +++++++++++- resources/qml/RoomSettings.qml | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 9dac5830..2be5fe92 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -61,9 +61,19 @@ Page { } } + Platform.MessageDialog { + id: leaveRoomDialog + + title: qsTr("Leave Room") + text: qsTr("Are you sure you want to leave this room?") + modality: Qt.Modal + onAccepted: Rooms.leave(roomContextMenu.roomid) + buttons: Dialog.Ok | Dialog.Cancel + } + Platform.MenuItem { text: qsTr("Leave room") - onTriggered: Rooms.leave(roomContextMenu.roomid) + onTriggered: leaveRoomDialog.open() } Platform.MenuSeparator { diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index b4936f3e..11b9fa2a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -219,7 +219,7 @@ ApplicationWindow { title: qsTr("End-to-End Encryption") text: qsTr("Encryption is currently experimental and things might break unexpectedly.
Please take note that it can't be disabled afterwards.") - modality: Qt.NonModal + modality: Qt.Modal onAccepted: { if (roomSettings.isEncryptionEnabled) return ; -- cgit 1.5.1 From 50cc0fca3b01f65fd2fe0707736160c92bc35f21 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 22 Jul 2021 15:31:07 +0200 Subject: Fix emoji picker not connected to input --- resources/qml/MessageInput.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index c135aff9..58d71a4e 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -355,7 +355,7 @@ Rectangle { image: ":/icons/icons/ui/smile.png" ToolTip.visible: hovered ToolTip.text: qsTr("Emoji") - onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(function(emoji) { + onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) { messageInput.insert(messageInput.cursorPosition, emoji); TimelineManager.focusMessageInput(); }) -- cgit 1.5.1 From 0c798554b54e25e03cd7a211f278362bd3dc2630 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 01:03:38 +0200 Subject: Rename image pack model --- CMakeLists.txt | 4 +- src/CombinedImagePackModel.cpp | 76 ++++++++++++++++++++++++++++++++++++ src/CombinedImagePackModel.h | 48 +++++++++++++++++++++++ src/ImagePackModel.cpp | 74 ----------------------------------- src/ImagePackModel.h | 48 ----------------------- src/timeline/InputBar.cpp | 4 +- src/timeline/InputBar.h | 4 +- src/timeline/TimelineViewManager.cpp | 6 +-- 8 files changed, 133 insertions(+), 131 deletions(-) create mode 100644 src/CombinedImagePackModel.cpp create mode 100644 src/CombinedImagePackModel.h delete mode 100644 src/ImagePackModel.cpp delete mode 100644 src/ImagePackModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f77d9978..b802d37c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -354,7 +354,7 @@ set(SRC_FILES src/Olm.cpp src/RegisterPage.cpp src/SSOHandler.cpp - src/ImagePackModel.cpp + src/CombinedImagePackModel.cpp src/TrayIcon.cpp src/UserSettingsPage.cpp src/UsersModel.cpp @@ -558,7 +558,7 @@ qt5_wrap_cpp(MOC_HEADERS src/MxcImageProvider.h src/RegisterPage.h src/SSOHandler.h - src/ImagePackModel.h + src/CombinedImagePackModel.h src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h diff --git a/src/CombinedImagePackModel.cpp b/src/CombinedImagePackModel.cpp new file mode 100644 index 00000000..c5b5b886 --- /dev/null +++ b/src/CombinedImagePackModel.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "CombinedImagePackModel.h" + +#include "Cache_p.h" +#include "CompletionModelRoles.h" + +CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, + bool stickers, + QObject *parent) + : QAbstractListModel(parent) + , room_id(roomId) +{ + auto packs = cache::client()->getImagePacks(room_id, stickers); + + for (const auto &pack : packs) { + QString packname = QString::fromStdString(pack.packname); + + for (const auto &img : pack.images) { + ImageDesc i{}; + i.shortcode = QString::fromStdString(img.first); + i.packname = packname; + i.image = img.second; + images.push_back(std::move(i)); + } + } +} + +int +CombinedImagePackModel::rowCount(const QModelIndex &) const +{ + return (int)images.size(); +} + +QHash +CombinedImagePackModel::roleNames() const +{ + return { + {CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::Url, "url"}, + {Roles::ShortCode, "shortcode"}, + {Roles::Body, "body"}, + {Roles::PackName, "packname"}, + {Roles::OriginalRow, "originalRow"}, + }; +} + +QVariant +CombinedImagePackModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + return QString::fromStdString(images[index.row()].image.url); + case Roles::Url: + return QString::fromStdString(images[index.row()].image.url); + case CompletionModel::SearchRole: + case Roles::ShortCode: + return images[index.row()].shortcode; + case CompletionModel::SearchRole2: + case Roles::Body: + return QString::fromStdString(images[index.row()].image.body); + case Roles::PackName: + return images[index.row()].packname; + case Roles::OriginalRow: + return index.row(); + default: + return {}; + } + } + return {}; +} diff --git a/src/CombinedImagePackModel.h b/src/CombinedImagePackModel.h new file mode 100644 index 00000000..f0f69799 --- /dev/null +++ b/src/CombinedImagePackModel.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +class CombinedImagePackModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + PackName, + OriginalRow, + }; + + CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + mtx::events::msc2545::PackImage imageAt(int row) + { + if (row < 0 || static_cast(row) >= images.size()) + return {}; + return images.at(static_cast(row)).image; + } + +private: + std::string room_id; + + struct ImageDesc + { + QString shortcode; + QString packname; + + mtx::events::msc2545::PackImage image; + }; + + std::vector images; +}; diff --git a/src/ImagePackModel.cpp b/src/ImagePackModel.cpp deleted file mode 100644 index 9b0dca8d..00000000 --- a/src/ImagePackModel.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "ImagePackModel.h" - -#include "Cache_p.h" -#include "CompletionModelRoles.h" - -ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject *parent) - : QAbstractListModel(parent) - , room_id(roomId) -{ - auto packs = cache::client()->getImagePacks(room_id, stickers); - - for (const auto &pack : packs) { - QString packname = QString::fromStdString(pack.packname); - - for (const auto &img : pack.images) { - ImageDesc i{}; - i.shortcode = QString::fromStdString(img.first); - i.packname = packname; - i.image = img.second; - images.push_back(std::move(i)); - } - } -} - -int -ImagePackModel::rowCount(const QModelIndex &) const -{ - return (int)images.size(); -} - -QHash -ImagePackModel::roleNames() const -{ - return { - {CompletionModel::CompletionRole, "completionRole"}, - {CompletionModel::SearchRole, "searchRole"}, - {CompletionModel::SearchRole2, "searchRole2"}, - {Roles::Url, "url"}, - {Roles::ShortCode, "shortcode"}, - {Roles::Body, "body"}, - {Roles::PackName, "packname"}, - {Roles::OriginalRow, "originalRow"}, - }; -} - -QVariant -ImagePackModel::data(const QModelIndex &index, int role) const -{ - if (hasIndex(index.row(), index.column(), index.parent())) { - switch (role) { - case CompletionModel::CompletionRole: - return QString::fromStdString(images[index.row()].image.url); - case Roles::Url: - return QString::fromStdString(images[index.row()].image.url); - case CompletionModel::SearchRole: - case Roles::ShortCode: - return images[index.row()].shortcode; - case CompletionModel::SearchRole2: - case Roles::Body: - return QString::fromStdString(images[index.row()].image.body); - case Roles::PackName: - return images[index.row()].packname; - case Roles::OriginalRow: - return index.row(); - default: - return {}; - } - } - return {}; -} diff --git a/src/ImagePackModel.h b/src/ImagePackModel.h deleted file mode 100644 index 937014ec..00000000 --- a/src/ImagePackModel.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include - -class ImagePackModel : public QAbstractListModel -{ - Q_OBJECT -public: - enum Roles - { - Url = Qt::UserRole, - ShortCode, - Body, - PackName, - OriginalRow, - }; - - ImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr); - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - - mtx::events::msc2545::PackImage imageAt(int row) - { - if (row < 0 || static_cast(row) >= images.size()) - return {}; - return images.at(static_cast(row)).image; - } - -private: - std::string room_id; - - struct ImageDesc - { - QString shortcode; - QString packname; - - mtx::events::msc2545::PackImage image; - }; - - std::vector images; -}; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 56d0d1ce..f17081e5 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -19,9 +19,9 @@ #include "Cache.h" #include "ChatPage.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "Config.h" -#include "ImagePackModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" @@ -503,7 +503,7 @@ InputBar::video(const QString &filename, } void -InputBar::sticker(ImagePackModel *model, int row) +InputBar::sticker(CombinedImagePackModel *model, int row) { if (!model || row < 0) return; diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index acedceb7..2e6fb5c0 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -12,7 +12,7 @@ #include class TimelineModel; -class ImagePackModel; +class CombinedImagePackModel; class QMimeData; class QDropEvent; class QStringList; @@ -58,7 +58,7 @@ public slots: MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED, bool rainbowify = false); void reaction(const QString &reactedEvent, const QString &reactionKey); - void sticker(ImagePackModel *model, int row); + void sticker(CombinedImagePackModel *model, int row); private slots: void startTyping(); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index b1643798..2da7d789 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -15,11 +15,11 @@ #include "ChatPage.h" #include "Clipboard.h" #include "ColorImageProvider.h" +#include "CombinedImagePackModel.h" #include "CompletionProxyModel.h" #include "DelegateChooser.h" #include "DeviceVerificationFlow.h" #include "EventAccessors.h" -#include "ImagePackModel.h" #include "InviteesModel.h" #include "Logging.h" #include "MainWindow.h" @@ -146,7 +146,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", @@ -622,7 +622,7 @@ TimelineViewManager::completerFor(QString completerName, QString roomId) roomModel->setParent(proxy); return proxy; } else if (completerName == "stickers") { - auto stickerModel = new ImagePackModel(roomId.toStdString(), true); + auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true); auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast(-1) / 4); stickerModel->setParent(proxy); return proxy; -- cgit 1.5.1 From eafbab6ae128dd5b14f145ba9214a6673e982951 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 21 Jul 2021 13:37:57 +0200 Subject: Add menu to enable or disable stickers globally --- CMakeLists.txt | 4 + resources/qml/RoomSettings.qml | 11 + resources/qml/Root.qml | 15 ++ resources/qml/dialogs/ImagePackSettingsDialog.qml | 309 ++++++++++++++++++++++ resources/res.qrc | 1 + src/Cache.cpp | 33 ++- src/CacheStructs.h | 5 +- src/Cache_p.h | 9 +- src/CombinedImagePackModel.cpp | 5 +- src/ImagePackListModel.cpp | 76 ++++++ src/ImagePackListModel.h | 37 +++ src/SingleImagePackModel.cpp | 100 +++++++ src/SingleImagePackModel.h | 61 +++++ src/timeline/TimelineViewManager.cpp | 20 ++ src/timeline/TimelineViewManager.h | 3 + src/ui/RoomSettings.h | 2 +- 16 files changed, 674 insertions(+), 17 deletions(-) create mode 100644 resources/qml/dialogs/ImagePackSettingsDialog.qml create mode 100644 src/ImagePackListModel.cpp create mode 100644 src/ImagePackListModel.h create mode 100644 src/SingleImagePackModel.cpp create mode 100644 src/SingleImagePackModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b802d37c..90ad1276 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -355,6 +355,8 @@ set(SRC_FILES src/RegisterPage.cpp src/SSOHandler.cpp src/CombinedImagePackModel.cpp + src/SingleImagePackModel.cpp + src/ImagePackListModel.cpp src/TrayIcon.cpp src/UserSettingsPage.cpp src/UsersModel.cpp @@ -559,6 +561,8 @@ qt5_wrap_cpp(MOC_HEADERS src/RegisterPage.h src/SSOHandler.h src/CombinedImagePackModel.h + src/SingleImagePackModel.h + src/ImagePackListModel.h src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 11b9fa2a..accb5637 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -249,6 +249,17 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } + MatrixText { + text: qsTr("Sticker & Emote Settings") + } + + Button { + text: qsTr("Change") + ToolTip.text: qsTr("Change what packs are enabled, remove packs or create new ones") + onClicked: TimelineManager.openImagePackSettings(roomSettings.roomId) + Layout.alignment: Qt.AlignRight + } + Item { // for adding extra space between sections Layout.fillWidth: true diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 8e226639..1793d9bc 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -4,6 +4,7 @@ import "./delegates" import "./device-verification" +import "./dialogs" import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform @@ -87,6 +88,14 @@ Page { } + Component { + id: packSettingsComponent + + ImagePackSettingsDialog { + } + + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -120,6 +129,12 @@ Page { }); userProfile.show(); } + onShowImagePackSettings: { + var packSet = packSettingsComponent.createObject(timelineRoot, { + "packlist": packlist + }); + packSet.show(); + } } Connections { diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml new file mode 100644 index 00000000..c4b4a885 --- /dev/null +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -0,0 +1,309 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import "../components" +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import im.nheko 1.0 + +ApplicationWindow { + id: win + + property ImagePackListModel packlist + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + property SingleImagePackModel currentPack: packlist.packAt(currentPackIndex) + property int currentPackIndex: 0 + readonly property int stickerDim: 128 + readonly property int stickerDimPad: 128 + Nheko.paddingSmall + + title: qsTr("Image pack settings") + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 400 + width: 600 + palette: Nheko.colors + color: Nheko.colors.base + modality: Qt.NonModal + flags: Qt.Dialog + + AdaptiveLayout { + id: adaptiveView + + anchors.fill: parent + singlePageMode: false + pageIndex: 0 + + AdaptiveLayoutElement { + id: packlistC + + visible: Settings.groupView + minimumWidth: 200 + collapsedWidth: 200 + preferredWidth: 300 + maximumWidth: 300 + + ListView { + model: packlist + clip: true + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + delegate: Rectangle { + id: packItem + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property color bubbleBackground: Nheko.colors.highlight + property color bubbleText: Nheko.colors.highlightedText + required property string displayName + required property string avatarUrl + required property bool fromAccountData + required property bool fromCurrentRoom + required property int index + + color: background + height: avatarSize + 2 * Nheko.paddingMedium + width: ListView.view.width + state: "normal" + states: [ + State { + name: "highlight" + when: hovered.hovered && !(index == currentPackIndex) + + PropertyChanges { + target: packItem + background: Nheko.colors.dark + importantText: Nheko.colors.brightText + unimportantText: Nheko.colors.brightText + bubbleBackground: Nheko.colors.highlight + bubbleText: Nheko.colors.highlightedText + } + + }, + State { + name: "selected" + when: index == currentPackIndex + + PropertyChanges { + target: packItem + background: Nheko.colors.highlight + importantText: Nheko.colors.highlightedText + unimportantText: Nheko.colors.highlightedText + bubbleBackground: Nheko.colors.highlightedText + bubbleText: Nheko.colors.highlight + } + + } + ] + + TapHandler { + margin: -Nheko.paddingSmall + onSingleTapped: currentPackIndex = index + } + + HoverHandler { + id: hovered + } + + RowLayout { + spacing: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + + Avatar { + // In the future we could show an online indicator by setting the userid for the avatar + //userid: Nheko.currentUser.userid + + id: avatar + + enabled: false + Layout.alignment: Qt.AlignVCenter + height: avatarSize + width: avatarSize + url: avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: packItem.displayName + } + + ColumnLayout { + id: textContent + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.minimumWidth: 100 + width: parent.width - avatar.width + Layout.preferredWidth: parent.width - avatar.width + spacing: Nheko.paddingSmall + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: packItem.importantText + elideWidth: textContent.width - Nheko.paddingMedium + fullText: displayName + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + color: packItem.unimportantText + font.pixelSize: fontMetrics.font.pixelSize * 0.9 + elideWidth: textContent.width - Nheko.paddingSmall + fullText: { + if (fromAccountData) + return qsTr("Private pack"); + else if (fromCurrentRoom) + return qsTr("Pack from this room"); + else + return qsTr("Globally enabled pack"); + } + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + } + + } + + } + + } + + } + + AdaptiveLayoutElement { + id: packinfoC + + Rectangle { + color: Nheko.colors.window + + ColumnLayout { + id: packinfo + + property string packName: currentPack ? currentPack.packname : "" + property string avatarUrl: currentPack ? currentPack.avatarUrl : "" + + anchors.fill: parent + anchors.margins: Nheko.paddingLarge + spacing: Nheko.paddingLarge + + Avatar { + url: packinfo.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: packinfo.packName + height: 100 + width: 100 + Layout.alignment: Qt.AlignHCenter + enabled: false + } + + MatrixText { + text: packinfo.packName + font.pixelSize: 24 + Layout.alignment: Qt.AlignHCenter + } + + GridLayout { + Layout.alignment: Qt.AlignHCenter + visible: currentPack && currentPack.roomid != "" + columns: 2 + rowSpacing: Nheko.paddingMedium + + MatrixText { + text: qsTr("Enable globally") + } + + ToggleButton { + ToolTip.text: qsTr("Enables this pack to be used in all rooms") + checked: currentPack ? currentPack.isGloballyEnabled : false + onClicked: currentPack.isGloballyEnabled = !currentPack.isGloballyEnabled + Layout.alignment: Qt.AlignRight + } + + } + + GridView { + Layout.fillHeight: true + Layout.fillWidth: true + model: currentPack + cellWidth: stickerDimPad + cellHeight: stickerDimPad + boundsBehavior: Flickable.StopAtBounds + clip: true + currentIndex: -1 // prevent sorting from stealing focus + cacheBuffer: 500 + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + // Individual emoji + delegate: AbstractButton { + width: stickerDim + height: stickerDim + hoverEnabled: true + ToolTip.text: ":" + model.shortcode + ": - " + model.body + ToolTip.visible: hovered + + contentItem: Image { + height: stickerDim + width: stickerDim + source: model.url.replace("mxc://", "image://MxcImage/") + fillMode: Image.PreserveAspectFit + } + + background: Rectangle { + anchors.fill: parent + color: hovered ? Nheko.colors.highlight : 'transparent' + radius: 5 + } + + } + + } + + } + + } + + } + + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Close") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + onClicked: win.close() + } + + } + +} diff --git a/resources/res.qrc b/resources/res.qrc index f8c040e4..5d37c397 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -160,6 +160,7 @@ qml/device-verification/Failed.qml qml/device-verification/Success.qml qml/dialogs/InputDialog.qml + qml/dialogs/ImagePackSettingsDialog.qml qml/ui/Ripple.qml qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml diff --git a/src/Cache.cpp b/src/Cache.cpp index 0bcf9fbf..d651b182 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3383,26 +3383,30 @@ Cache::getChildRoomIds(const std::string &room_id) } std::vector -Cache::getImagePacks(const std::string &room_id, bool stickers) +Cache::getImagePacks(const std::string &room_id, std::optional stickers) { auto txn = ro_txn(env_); std::vector infos; - auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack) { - if (!pack.pack || (stickers ? pack.pack->is_sticker() : pack.pack->is_emoji())) { + auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack, + const std::string &source_room, + const std::string &state_key) { + if (!pack.pack || !stickers.has_value() || + (stickers.value() ? pack.pack->is_sticker() : pack.pack->is_emoji())) { ImagePackInfo info; - if (pack.pack) - info.packname = pack.pack->display_name; + info.source_room = source_room; + info.state_key = state_key; + info.pack.pack = pack.pack; for (const auto &img : pack.images) { if (img.second.overrides_usage() && (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) continue; - info.images.insert(img); + info.pack.images.insert(img); } - if (!info.images.empty()) + if (!info.pack.images.empty()) infos.push_back(std::move(info)); } }; @@ -3414,7 +3418,7 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) std::get_if>( &*accountpack); if (tmp) - addPack(tmp->content); + addPack(tmp->content, "", ""); } // packs from rooms, that were enabled globally @@ -3433,7 +3437,7 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) if (auto pack = getStateEvent( txn, room_id2, state_id)) - addPack(pack->content); + addPack(pack->content, room_id2, state_id); } } } @@ -3441,16 +3445,23 @@ Cache::getImagePacks(const std::string &room_id, bool stickers) // packs from current room if (auto pack = getStateEvent(txn, room_id)) { - addPack(pack->content); + addPack(pack->content, room_id, ""); } for (const auto &pack : getStateEventsWithType(txn, room_id)) { - addPack(pack.content); + addPack(pack.content, room_id, pack.state_key); } return infos; } +std::optional +Cache::getAccountData(mtx::events::EventType type, const std::string &room_id) +{ + auto txn = ro_txn(env_); + return getAccountData(txn, type, room_id); +} + std::optional Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id) { diff --git a/src/CacheStructs.h b/src/CacheStructs.h index f274d70f..4a5c5c76 100644 --- a/src/CacheStructs.h +++ b/src/CacheStructs.h @@ -113,6 +113,7 @@ struct RoomSearchResult struct ImagePackInfo { - std::string packname; - std::map images; + mtx::events::msc2545::ImagePack pack; + std::string source_room; + std::string state_key; }; diff --git a/src/Cache_p.h b/src/Cache_p.h index 13fbc371..c9d42202 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -97,6 +97,12 @@ public: return getStateEvent(txn, room_id, state_key); } + //! retrieve a specific event from account data + //! pass empty room_id for global account data + std::optional getAccountData( + mtx::events::EventType type, + const std::string &room_id = ""); + //! Retrieve member info from a room. std::vector getMembers(const std::string &room_id, std::size_t startIndex = 0, @@ -225,7 +231,8 @@ public: std::vector getParentRoomIds(const std::string &room_id); std::vector getChildRoomIds(const std::string &room_id); - std::vector getImagePacks(const std::string &room_id, bool stickers); + std::vector getImagePacks(const std::string &room_id, + std::optional stickers); //! Mark a room that uses e2e encryption. void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id); diff --git a/src/CombinedImagePackModel.cpp b/src/CombinedImagePackModel.cpp index c5b5b886..341a34ec 100644 --- a/src/CombinedImagePackModel.cpp +++ b/src/CombinedImagePackModel.cpp @@ -16,9 +16,10 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, auto packs = cache::client()->getImagePacks(room_id, stickers); for (const auto &pack : packs) { - QString packname = QString::fromStdString(pack.packname); + QString packname = + pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : ""; - for (const auto &img : pack.images) { + for (const auto &img : pack.pack.images) { ImageDesc i{}; i.shortcode = QString::fromStdString(img.first); i.packname = packname; diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp new file mode 100644 index 00000000..89f1f68e --- /dev/null +++ b/src/ImagePackListModel.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ImagePackListModel.h" + +#include + +#include "Cache_p.h" +#include "SingleImagePackModel.h" + +ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *parent) + : QAbstractListModel(parent) + , room_id(roomId) +{ + auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt); + + for (const auto &pack : packs_) { + packs.push_back( + QSharedPointer(new SingleImagePackModel(pack))); + } +} + +int +ImagePackListModel::rowCount(const QModelIndex &) const +{ + return (int)packs.size(); +} + +QHash +ImagePackListModel::roleNames() const +{ + return { + {Roles::DisplayName, "displayName"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::FromAccountData, "fromAccountData"}, + {Roles::FromCurrentRoom, "fromCurrentRoom"}, + {Roles::StateKey, "statekey"}, + {Roles::RoomId, "roomid"}, + }; +} + +QVariant +ImagePackListModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &pack = packs.at(index.row()); + switch (role) { + case Roles::DisplayName: + return pack->packname(); + case Roles::AvatarUrl: + return pack->avatarUrl(); + case Roles::FromAccountData: + return pack->roomid().isEmpty(); + case Roles::FromCurrentRoom: + return pack->roomid().toStdString() == this->room_id; + case Roles::StateKey: + return pack->statekey(); + case Roles::RoomId: + return pack->roomid(); + default: + return {}; + } + } + return {}; +} + +SingleImagePackModel * +ImagePackListModel::packAt(int row) +{ + if (row < 0 || static_cast(row) >= packs.size()) + return {}; + auto e = packs.at(row).get(); + QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); + return e; +} diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h new file mode 100644 index 00000000..0a044690 --- /dev/null +++ b/src/ImagePackListModel.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class SingleImagePackModel; +class ImagePackListModel : public QAbstractListModel +{ + Q_OBJECT +public: + enum Roles + { + DisplayName = Qt::UserRole, + AvatarUrl, + FromAccountData, + FromCurrentRoom, + StateKey, + RoomId, + }; + + ImagePackListModel(const std::string &roomId, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE SingleImagePackModel *packAt(int row); + +private: + std::string room_id; + + std::vector> packs; +}; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp new file mode 100644 index 00000000..ba873c19 --- /dev/null +++ b/src/SingleImagePackModel.cpp @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "SingleImagePackModel.h" + +#include "Cache_p.h" +#include "MatrixClient.h" + +SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) + : QAbstractListModel(parent) + , roomid_(std::move(pack_.source_room)) + , statekey_(std::move(pack_.state_key)) + , pack(std::move(pack_.pack)) +{ + if (!pack.pack) + pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; + + for (const auto &e : pack.images) + shortcodes.push_back(e.first); +} + +int +SingleImagePackModel::rowCount(const QModelIndex &) const +{ + return (int)shortcodes.size(); +} + +QHash +SingleImagePackModel::roleNames() const +{ + return { + {Roles::Url, "url"}, + {Roles::ShortCode, "shortCode"}, + {Roles::Body, "body"}, + {Roles::IsEmote, "isEmote"}, + {Roles::IsSticker, "isSticker"}, + }; +} + +QVariant +SingleImagePackModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + const auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case Url: + return QString::fromStdString(img.url); + case ShortCode: + return QString::fromStdString(shortcodes.at(index.row())); + case Body: + return QString::fromStdString(img.body); + case IsEmote: + return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + case IsSticker: + return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + default: + return {}; + } + } + return {}; +} + +bool +SingleImagePackModel::isGloballyEnabled() const +{ + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent>( + &*roomPacks)) { + if (tmp->content.rooms.count(roomid_) && + tmp->content.rooms.at(roomid_).count(statekey_)) + return true; + } + } + return false; +} +void +SingleImagePackModel::setGloballyEnabled(bool enabled) +{ + mtx::events::msc2545::ImagePackRooms content{}; + if (auto roomPacks = + cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) { + if (auto tmp = std::get_if< + mtx::events::EphemeralEvent>( + &*roomPacks)) { + content = tmp->content; + } + } + + if (enabled) + content.rooms[roomid_][statekey_] = {}; + else + content.rooms[roomid_].erase(statekey_); + + http::client()->put_account_data(content, [this](mtx::http::RequestErr) { + // emit this->globallyEnabledChanged(); + }); +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h new file mode 100644 index 00000000..e0c791ba --- /dev/null +++ b/src/SingleImagePackModel.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include + +#include "CacheStructs.h" + +class SingleImagePackModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString roomid READ roomid CONSTANT) + Q_PROPERTY(QString statekey READ statekey CONSTANT) + Q_PROPERTY(QString attribution READ statekey CONSTANT) + Q_PROPERTY(QString packname READ packname CONSTANT) + Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) + Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT) + Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT) + Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY + globallyEnabledChanged) +public: + enum Roles + { + Url = Qt::UserRole, + ShortCode, + Body, + IsEmote, + IsSticker, + }; + + SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + + QString roomid() const { return QString::fromStdString(roomid_); } + QString statekey() const { return QString::fromStdString(statekey_); } + QString packname() const { return QString::fromStdString(pack.pack->display_name); } + QString attribution() const { return QString::fromStdString(pack.pack->attribution); } + QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); } + bool isStickerPack() const { return pack.pack->is_sticker(); } + bool isEmotePack() const { return pack.pack->is_emoji(); } + + bool isGloballyEnabled() const; + void setGloballyEnabled(bool enabled); + +signals: + void globallyEnabledChanged(); + +private: + std::string roomid_; + std::string statekey_; + + mtx::events::msc2545::ImagePack pack; + std::vector shortcodes; +}; diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 2da7d789..4353ef62 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -20,12 +20,14 @@ #include "DelegateChooser.h" #include "DeviceVerificationFlow.h" #include "EventAccessors.h" +#include "ImagePackListModel.h" #include "InviteesModel.h" #include "Logging.h" #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" #include "RoomsModel.h" +#include "SingleImagePackModel.h" #include "UserSettingsPage.h" #include "UsersModel.h" #include "dialogs/ImageOverlay.h" @@ -185,6 +187,18 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par "Room Settings needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "ImagePackListModel", + "ImagePackListModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "SingleImagePackModel", + "SingleImagePackModel needs to be instantiated on the C++ side"); qmlRegisterUncreatableType( "im.nheko", 1, @@ -436,6 +450,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) }); } +void +TimelineViewManager::openImagePackSettings(QString roomid) +{ + emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this)); +} + void TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img) { diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 374685e3..bdec405a 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -33,6 +33,7 @@ class ColorImageProvider; class UserSettings; class ChatPage; class DeviceVerificationFlow; +class ImagePackListModel; class TimelineViewManager : public QObject { @@ -57,6 +58,7 @@ public: Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; } bool isWindowFocused() const { return isWindowFocused_; } Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId); + Q_INVOKABLE void openImagePackSettings(QString roomid); Q_INVOKABLE QColor userColor(QString id, QColor background); Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); } @@ -93,6 +95,7 @@ signals: void openRoomSettingsDialog(RoomSettings *settings); void openInviteUsersDialog(InviteesModel *invitees); void openProfile(UserProfile *profile); + void showImagePackSettings(ImagePackListModel *packlist); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 367f3111..cf36f795 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -136,4 +136,4 @@ private: RoomInfo info_; int notifications_ = 0; int accessRules_ = 0; -}; \ No newline at end of file +}; -- cgit 1.5.1 From e5fec03d273557e0c4e62955956b3f78b1d4cb8c Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 23 Jul 2021 17:58:17 +0200 Subject: Update mtxclient --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90ad1276..7d2d5dc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + GIT_TAG bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 ) 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 b6f468db..a3637fd0 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -161,7 +161,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 1c277e9ac69aafdaf6888ce595b21dc86e970f28 + - commit: bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: -- cgit 1.5.1 From dba2fb525a4c7a58f189995c1b323be23e7aab41 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 23 Jul 2021 18:21:55 +0200 Subject: Remove unused this capture --- src/SingleImagePackModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index ba873c19..6c508da0 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -94,7 +94,7 @@ SingleImagePackModel::setGloballyEnabled(bool enabled) else content.rooms[roomid_].erase(statekey_); - http::client()->put_account_data(content, [this](mtx::http::RequestErr) { + http::client()->put_account_data(content, [](mtx::http::RequestErr) { // emit this->globallyEnabledChanged(); }); } -- cgit 1.5.1 From bef2cbbd7f0a66fb552d6b19a7ebc706f9e4b7ad Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 23 Jul 2021 19:08:44 +0200 Subject: Try to avoid more olm loops --- src/Olm.cpp | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 18e2ddcf..0c9962f9 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -214,12 +214,14 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey const auto my_key = olm::client()->identity_keys().curve25519; + bool failed_decryption = false; + for (const auto &cipher : msg.ciphertext) { // We skip messages not meant for the current device. if (cipher.first != my_key) { nhlog::crypto()->debug( "Skipping message for {} since we are {}.", cipher.first, my_key); - return; + continue; } const auto type = cipher.second.type; @@ -234,6 +236,7 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey msg.sender, msg.sender_key, cipher.second); } else { nhlog::crypto()->error("Undecryptable olm message!"); + failed_decryption = true; continue; } } @@ -423,22 +426,28 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey } return; + } else { + failed_decryption = true; } } - try { - std::map> targets; - for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - if (key.keys.at("curve25519:" + device_id) == msg.sender_key) - targets[msg.sender].push_back(device_id); - } + if (failed_decryption) { + try { + std::map> targets; + for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { + if (key.keys.at("curve25519:" + device_id) == msg.sender_key) + targets[msg.sender].push_back(device_id); + } - send_encrypted_to_device_messages( - targets, mtx::events::DeviceEvent{}, true); - nhlog::crypto()->info( - "Recovering from broken olm channel with {}:{}", msg.sender, msg.sender_key); - } catch (std::exception &e) { - nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", e.what()); + send_encrypted_to_device_messages( + targets, mtx::events::DeviceEvent{}, true); + nhlog::crypto()->info("Recovering from broken olm channel with {}:{}", + msg.sender, + msg.sender_key); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to recover from broken olm sessions: {}", + e.what()); + } } } -- cgit 1.5.1 From ace16b8c4db6c292df1ac38f7ea385a13f5bac00 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 24 Jul 2021 14:12:34 +0200 Subject: Fix some issues when parsing or serializing enabled pack rooms --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7d2d5dc3..7b26602c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,7 +382,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 + GIT_TAG 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb ) 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 a3637fd0..0fa450b3 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -161,7 +161,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: bc203a0e01abcff85ae1972f1ab9de3fabc3dba6 + - commit: 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: -- cgit 1.5.1 From a61678242b91944c07bf4b82b4eb25baad0db00b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 24 Jul 2021 14:59:14 +0200 Subject: Fix edge case that could lead to no new one time keys being uploaded --- src/ChatPage.cpp | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 6b8c1e10..615a15b3 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -929,31 +929,33 @@ ChatPage::currentPresence() const void ChatPage::ensureOneTimeKeyCount(const std::map &counts) { - for (const auto &entry : counts) { - if (entry.second < MAX_ONETIME_KEYS) { - const int nkeys = MAX_ONETIME_KEYS - entry.second; + uint16_t count = 0; + if (auto c = counts.find(mtx::crypto::SIGNED_CURVE25519); c != counts.end()) + count = c->second; - nhlog::crypto()->info("uploading {} {} keys", nkeys, entry.first); - olm::client()->generate_one_time_keys(nkeys); + if (count < MAX_ONETIME_KEYS) { + const int nkeys = MAX_ONETIME_KEYS - count; - http::client()->upload_keys( - olm::client()->create_upload_keys_request(), - [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { - if (err) { - nhlog::crypto()->warn( - "failed to update one-time keys: {} {} {}", - err->matrix_error.error, - static_cast(err->status_code), - static_cast(err->error_code)); + nhlog::crypto()->info( + "uploading {} {} keys", nkeys, mtx::crypto::SIGNED_CURVE25519); + olm::client()->generate_one_time_keys(nkeys); - if (err->status_code < 400 || err->status_code >= 500) - return; - } + http::client()->upload_keys( + olm::client()->create_upload_keys_request(), + [](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) { + if (err) { + nhlog::crypto()->warn("failed to update one-time keys: {} {} {}", + err->matrix_error.error, + static_cast(err->status_code), + static_cast(err->error_code)); - // mark as published anyway, otherwise we may end up in a loop. - olm::mark_keys_as_published(); - }); - } + if (err->status_code < 400 || err->status_code >= 500) + return; + } + + // mark as published anyway, otherwise we may end up in a loop. + olm::mark_keys_as_published(); + }); } } -- cgit 1.5.1 From fb3c443ce06dd516e409351cb699a322b92456e0 Mon Sep 17 00:00:00 2001 From: Joe Date: Sat, 24 Jul 2021 18:27:59 -0400 Subject: Remove additional key requests preference code --- src/Utils.cpp | 10 ---------- src/Utils.h | 3 --- src/ui/RoomSettings.cpp | 7 ------- src/ui/RoomSettings.h | 2 -- 4 files changed, 22 deletions(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index a99831c4..41013e39 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -172,16 +172,6 @@ utils::scaleFactor() return settings.value("settings/scale_factor", -1).toFloat(); } -void -utils::setKeyRequestsPreference(QString roomId, bool value) -{ - if (roomId.isEmpty()) - return; - - QSettings settings; - settings.setValue("rooms/respond_to_key_requests/" + roomId, value); -} - QString utils::descriptiveTime(const QDateTime &then) { diff --git a/src/Utils.h b/src/Utils.h index a50681ca..8f37a574 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -67,9 +67,6 @@ scaleFactor(); void setScaleFactor(float factor); -void -setKeyRequestsPreference(QString roomId, bool value); - //! Human friendly timestamp representation. QString descriptiveTime(const QDateTime &then); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index ae5fce0c..fcba8205 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -291,13 +291,6 @@ RoomSettings::accessJoinRules() return accessRules_; } -void -RoomSettings::changeKeyRequestsPreference(bool isOn) -{ - utils::setKeyRequestsPreference(roomid_, isOn); - emit keyRequestsChanged(); -} - void RoomSettings::enableEncryption() { diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 2deaa5e3..1c8b47d6 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -104,7 +104,6 @@ public: Q_INVOKABLE void openEditModal(); Q_INVOKABLE void changeAccessRules(int index); Q_INVOKABLE void changeNotifications(int currentIndex); - Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); signals: void loadingChanged(); @@ -112,7 +111,6 @@ signals: void roomTopicChanged(); void avatarUrlChanged(); void encryptionChanged(); - void keyRequestsChanged(); void notificationsChanged(); void accessJoinRulesChanged(); void displayError(const QString &errorMessage); -- cgit 1.5.1 From 453c8cd1f18daeb4eaedfe34f60d28acd7d1a2ed Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Sat, 24 Jul 2021 18:30:14 -0400 Subject: Update translations --- resources/langs/nheko_cs.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_de.ts | 99 ++++++++++++++++++++++++++++++---------- resources/langs/nheko_el.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_en.ts | 101 ++++++++++++++++++++++++++++++----------- resources/langs/nheko_eo.ts | 97 +++++++++++++++++++++++++++++---------- resources/langs/nheko_es.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_et.ts | 101 ++++++++++++++++++++++++++++++----------- resources/langs/nheko_fi.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_fr.ts | 101 ++++++++++++++++++++++++++++++----------- resources/langs/nheko_hu.ts | 101 ++++++++++++++++++++++++++++++----------- resources/langs/nheko_it.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_ja.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_ml.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_nl.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_pl.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_pt_BR.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_pt_PT.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_ro.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_ru.ts | 97 +++++++++++++++++++++++++++++---------- resources/langs/nheko_si.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_sv.ts | 95 +++++++++++++++++++++++++++++--------- resources/langs/nheko_zh_CN.ts | 95 +++++++++++++++++++++++++++++--------- 22 files changed, 1618 insertions(+), 504 deletions(-) diff --git a/resources/langs/nheko_cs.ts b/resources/langs/nheko_cs.ts index 7c1134aa..99813f81 100644 --- a/resources/langs/nheko_cs.ts +++ b/resources/langs/nheko_cs.ts @@ -157,12 +157,12 @@
- + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@
- + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1381,7 +1429,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1437,19 +1485,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1464,7 +1515,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1619,7 +1670,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1765,12 +1816,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1840,7 +1891,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2464,7 +2515,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2646,7 +2697,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_de.ts b/resources/langs/nheko_de.ts index 3cd35781..cb5b54fb 100644 --- a/resources/langs/nheko_de.ts +++ b/resources/langs/nheko_de.ts @@ -157,12 +157,12 @@ - + Confirm invite Einladung bestätigen - + Do you really want to invite %1 (%2)? Nutzer %1 (%2) wirklich einladen? @@ -227,12 +227,12 @@ Verbannung aufgehoben: %1 - + Do you really want to start a private chat with %1? Möchtest du wirklich eine private Konversation mit %1 beginnen? - + Cache migration failed! Migration des Caches fehlgeschlagen! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Geheimnisse entschlüsseln @@ -604,6 +604,44 @@ Nachricht weiterleiten + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Schließen + + InputBar @@ -910,7 +948,7 @@ Beispiel: https://mein.server:8787 Optionen - + &Copy &Kopieren @@ -1251,7 +1289,7 @@ Beispiel: https://mein.server:8787 RoomInfo - + no version stored keine Version gespeichert @@ -1268,6 +1306,16 @@ Beispiel: https://mein.server:8787 Enter the tag you want to use: Gib den Tag, den du verwenden willst, ein: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1384,7 +1432,7 @@ Beispiel: https://mein.server:8787 %1 Teilnehmer - + SETTINGS EINSTELLUNGEN @@ -1440,19 +1488,22 @@ Beispiel: https://mein.server:8787 Verschlüsselung ist derzeit experimentell und könnte unerwartete Probleme verursachen.<br>Bitte beachte, dass dies später nicht mehr deaktiviert werden kann. - - Respond to key requests - Auf Schlüsselanfragen antworten + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. - Soll der Client automatisch mit den Sitzungsschlüsseln auf Anfragen antworten oder nicht? Bitte mit Vorsicht benutzen, dies ist eine vorübergehende Maßnahme, um die Ende-zu-Ende-Implementierung zu testen, bis die Geräteverifizierung abgeschlossen ist. + + Change + - + + Change what packs are enabled, remove packs or create new ones + + + + INFO INFO @@ -1467,7 +1518,7 @@ Beispiel: https://mein.server:8787 Raumversion - + Failed to enable encryption: %1 Aktivierung der Verschlüsselung fehlgeschlagen: %1 @@ -1622,7 +1673,7 @@ Beispiel: https://mein.server:8787 TimelineModel - + Message redaction failed: %1 Nachricht zurückziehen fehlgeschlagen: %1 @@ -1767,12 +1818,12 @@ Beispiel: https://mein.server:8787 %1 hat das Anklopfen zurückgezogen. - + You joined this room. Du bist dem Raum beigetreten. - + %1 has changed their avatar and changed their display name to %2. %1 hat den eigenen Avatar und Namen geändert zu %2. @@ -1842,7 +1893,7 @@ Beispiel: https://mein.server:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Keinen verschlüsselten Chat mit diesem User gefunden. Erstelle einen verschlüsselten 1:1 Chat mit diesem Nutzer und versuche es erneut. @@ -2476,7 +2527,7 @@ Normalerweise animiert das den Taskbaricon oder färbt das Fenster orange ein. descriptiveTime - + Yesterday Gestern @@ -2660,7 +2711,7 @@ Medien-Größe: %2 message-description sent: - + You sent an audio clip Du hast eine Audiodatei gesendet diff --git a/resources/langs/nheko_el.ts b/resources/langs/nheko_el.ts index 6df73f73..d40a6433 100644 --- a/resources/langs/nheko_el.ts +++ b/resources/langs/nheko_el.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_en.ts b/resources/langs/nheko_en.ts index 97a67b06..1851fff1 100644 --- a/resources/langs/nheko_en.ts +++ b/resources/langs/nheko_en.ts @@ -157,12 +157,12 @@ - + Confirm invite Confirm invite - + Do you really want to invite %1 (%2)? Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Unbanned user: %1 - + Do you really want to start a private chat with %1? Do you really want to start a private chat with %1? - + Cache migration failed! Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decrypt secrets @@ -604,6 +604,44 @@ Forward Message + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Close + + InputBar @@ -910,7 +948,7 @@ Example: https://server.my:8787 Options - + &Copy &Copy @@ -1251,7 +1289,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored no version stored @@ -1268,6 +1306,16 @@ Example: https://server.my:8787 Enter the tag you want to use: Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1384,7 +1432,7 @@ Example: https://server.my:8787 %1 member(s) - + SETTINGS SETTINGS @@ -1440,21 +1488,22 @@ Example: https://server.my:8787 Encryption is currently experimental and things might break unexpectedly.<br>Please take note that it can't be disabled afterwards. - - Respond to key requests - Respond to key requests + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. - Whether or not the client should respond automatically with the session keys -upon request. Use with caution, this is a temporary measure to test the -E2E implementation until device verification is completed. + + Change + - + + Change what packs are enabled, remove packs or create new ones + + + + INFO INFO @@ -1469,7 +1518,7 @@ E2E implementation until device verification is completed. Room Version - + Failed to enable encryption: %1 Failed to enable encryption: %1 @@ -1624,7 +1673,7 @@ E2E implementation until device verification is completed. TimelineModel - + Message redaction failed: %1 Message redaction failed: %1 @@ -1769,12 +1818,12 @@ E2E implementation until device verification is completed. %1 redacted their knock. - + You joined this room. You joined this room. - + %1 has changed their avatar and changed their display name to %2. %1 has changed their avatar and changed their display name to %2. @@ -1844,7 +1893,7 @@ E2E implementation until device verification is completed. TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2479,7 +2528,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Yesterday @@ -2663,7 +2712,7 @@ Media size: %2 message-description sent: - + You sent an audio clip You sent an audio clip diff --git a/resources/langs/nheko_eo.ts b/resources/langs/nheko_eo.ts index f3529f28..a77c5c25 100644 --- a/resources/langs/nheko_eo.ts +++ b/resources/langs/nheko_eo.ts @@ -158,12 +158,12 @@ - + Confirm invite Konfirmu inviton - + Do you really want to invite %1 (%2)? Ĉu vi certe volas inviti uzanton %1 (%2)? @@ -228,12 +228,12 @@ Malforbaris uzanton: %1 - + Do you really want to start a private chat with %1? Ĉu vi certe volas komenci privatan babilon kun %1? - + Cache migration failed! Malsukcesis migrado de kaŝmemoro! @@ -353,7 +353,7 @@ CrossSigningSecrets - + Decrypt secrets Malĉifri sekretojn @@ -605,6 +605,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Fermi + + InputBar @@ -913,7 +951,7 @@ Ekzemplo: https://servilo.mia:8787 Elektebloj - + &Copy @@ -1254,7 +1292,7 @@ Ekzemplo: https://servilo.mia:8787 RoomInfo - + no version stored @@ -1271,6 +1309,16 @@ Ekzemplo: https://servilo.mia:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1387,7 +1435,7 @@ Ekzemplo: https://servilo.mia:8787 %1 ĉambrano(j) - + SETTINGS AGORDOJ @@ -1443,19 +1491,22 @@ Ekzemplo: https://servilo.mia:8787 Ĉifrado nun estas eksperimenta kaj povus rompiĝi neatendite.<br>Bonvole sciu, ke ne eblas ĝin malŝalti poste. - - Respond to key requests - Respondi al petoj de ŝlosiloj + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO INFORMOJ @@ -1470,7 +1521,7 @@ Ekzemplo: https://servilo.mia:8787 Versio de ĉambro - + Failed to enable encryption: %1 Malsukcesis ŝalti ĉifradon: %1 @@ -1626,7 +1677,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineModel - + Message redaction failed: %1 @@ -1773,12 +1824,12 @@ Ekzemplo: https://servilo.mia:8787 - + You joined this room. Vi aliĝis ĉi tiun ĉambron. - + %1 has changed their avatar and changed their display name to %2. @@ -1848,7 +1899,7 @@ Ekzemplo: https://servilo.mia:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2490,7 +2541,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Hieraŭ @@ -2672,7 +2723,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_es.ts b/resources/langs/nheko_es.ts index 8eb4675a..922e0500 100644 --- a/resources/langs/nheko_es.ts +++ b/resources/langs/nheko_es.ts @@ -157,12 +157,12 @@ - + Confirm invite Confirmar invitación - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1773,12 +1824,12 @@ Example: https://server.my:8787 - + You joined this room. Te has unido a esta sala. - + Rejected the knock from %1. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_et.ts b/resources/langs/nheko_et.ts index 20261395..57eac1c3 100644 --- a/resources/langs/nheko_et.ts +++ b/resources/langs/nheko_et.ts @@ -157,12 +157,12 @@ - + Confirm invite Kinnita kutse - + Do you really want to invite %1 (%2)? Kas sa tõesti soovid saata kutset kasutajale %1 (%2)? @@ -227,12 +227,12 @@ Suhtluskeeld eemaldatud: %1 - + Do you really want to start a private chat with %1? Kas sa kindlasti soovid alustada otsevestlust kasutajaga %1? - + Cache migration failed! Puhvri versiooniuuendus ebaõnnestus! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekrüpti andmed @@ -604,6 +604,44 @@ Suuna sõnum edasi + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Sulge + + InputBar @@ -910,7 +948,7 @@ Näiteks: https://server.minu:8787 Valikud - + &Copy &Kopeeri @@ -1251,7 +1289,7 @@ Näiteks: https://server.minu:8787 RoomInfo - + no version stored salvestatud versiooni ei leidu @@ -1268,6 +1306,16 @@ Näiteks: https://server.minu:8787 Enter the tag you want to use: Kirjuta silt, mida soovid kasutada: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1384,7 +1432,7 @@ Näiteks: https://server.minu:8787 %1 liige(t) - + SETTINGS SEADISTUSED @@ -1440,21 +1488,22 @@ Näiteks: https://server.minu:8787 Krüptimine on nhekos hetkel veel katseline ning nii mõndagi võib ootamatult katki minna. <br>Palun arvesta, et krüptimist ei saa hiljem enam välja lülitada. - - Respond to key requests - Vasta krüptovõtmete päringutele + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. - Kas klient peaks automaatselt vastama või mitte vastama sessioonivõtmete päringule. -Kasuta seda võimalust ettevaatlikult. Tegemist on ajutise lahendusega läbiva krüptimise -testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud. + + Change + - + + Change what packs are enabled, remove packs or create new ones + + + + INFO TEAVE @@ -1469,7 +1518,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< Jututoa versioon - + Failed to enable encryption: %1 Krüptimise kasutuselevõtmine ei õnnestunud: %1 @@ -1624,7 +1673,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineModel - + Message redaction failed: %1 Sõnumi ümbersõnastamine ebaõnnestus: %1 @@ -1769,12 +1818,12 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< %1 muutis oma koputust jututoa uksele. - + You joined this room. Sa liitusid jututoaga. - + %1 has changed their avatar and changed their display name to %2. %1 muutis oma tunnuspilti ja seadistas uueks kuvatavaks nimeks %2. @@ -1844,7 +1893,7 @@ testimiseks seni, kuni terviklik seadmete verifitseerimine on implementeeritud.< TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ühtegi krüptitud vestlust selle kasutajaga ei leidunud. Palun loo temaga krüptitud vestlus ja proovi uuesti. @@ -2479,7 +2528,7 @@ See tavaliselt tähendab, et rakenduse ikoon tegumiribal annab mingit sorti anim descriptiveTime - + Yesterday Eile @@ -2663,7 +2712,7 @@ Meedia suurus: %2 message-description sent: - + You sent an audio clip Sa saatsid helifaili diff --git a/resources/langs/nheko_fi.ts b/resources/langs/nheko_fi.ts index e884e0ac..422c2957 100644 --- a/resources/langs/nheko_fi.ts +++ b/resources/langs/nheko_fi.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ Välitä viesti + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Sulje + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 Asetukset - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored ei tallennettua versiota @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 Huoneen versio - + Failed to enable encryption: %1 Salauksen aktivointi epäonnistui: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Viestin muokkaus epäonnistui: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. Sinä liityit tähän huoneeseen. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Eilen @@ -2646,7 +2697,7 @@ Median koko: %2 message-description sent: - + You sent an audio clip Lähetit äänileikkeen diff --git a/resources/langs/nheko_fr.ts b/resources/langs/nheko_fr.ts index fb3c0e11..c6d42299 100644 --- a/resources/langs/nheko_fr.ts +++ b/resources/langs/nheko_fr.ts @@ -157,12 +157,12 @@ - + Confirm invite Confirmer l'invitation - + Do you really want to invite %1 (%2)? Voulez-vous vraiment inviter %1 (%2) ? @@ -227,12 +227,12 @@ %1 n'est plus banni(e) - + Do you really want to start a private chat with %1? Voulez-vous vraimer commencer une discussion privée avec %1 ? - + Cache migration failed! Échec de la migration du cache ! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Déchiffrer les secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Fermer + + InputBar @@ -910,7 +948,7 @@ Exemple : https ://monserveur.example.com :8787 Options - + &Copy @@ -1251,7 +1289,7 @@ Exemple : https ://monserveur.example.com :8787 RoomInfo - + no version stored pas de version enregistrée @@ -1268,6 +1306,16 @@ Exemple : https ://monserveur.example.com :8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1384,7 +1432,7 @@ Exemple : https ://monserveur.example.com :8787 %1 membre(s) - + SETTINGS CONFIGURATION @@ -1440,21 +1488,22 @@ Exemple : https ://monserveur.example.com :8787 Le chiffrement actuellement expérimental et des comportements inattendus peuvent être rencontrés. <br>Veuillez noter qu'il n'est pas possible de le désactiver par la suite. - - Respond to key requests - Répondre aux demandes de clé + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. - Si le client doit répondre automatiquement avec les clés de session lorsqu'elles sont -demandées. Utiliser précautionneusement, il s'agit d'une mesure temporaire afin de -tester le chiffrement de bout en bout tant que la vérification des appareils n'est pas au point. + + Change + - + + Change what packs are enabled, remove packs or create new ones + + + + INFO INFO @@ -1469,7 +1518,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& Version du salon - + Failed to enable encryption: %1 Échec de l'activation du chiffrement  : %1 @@ -1624,7 +1673,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineModel - + Message redaction failed: %1 Échec de la suppression du message : %1 @@ -1769,12 +1818,12 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& %1 ne frappe plus au salon. - + You joined this room. Vous avez rejoint ce salon. - + %1 has changed their avatar and changed their display name to %2. @@ -1844,7 +1893,7 @@ tester le chiffrement de bout en bout tant que la vérification des appareils n& TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Pas de discussion privée et chiffrée trouvée avec cet utilisateur. Créez-en une et réessayez. @@ -2481,7 +2530,7 @@ Cela met l'application en évidence dans la barre des tâches. descriptiveTime - + Yesterday Hier @@ -2665,7 +2714,7 @@ Taille du média : %2 message-description sent: - + You sent an audio clip Vous avez envoyé un message audio diff --git a/resources/langs/nheko_hu.ts b/resources/langs/nheko_hu.ts index a85f9ff3..7c29338c 100644 --- a/resources/langs/nheko_hu.ts +++ b/resources/langs/nheko_hu.ts @@ -157,12 +157,12 @@ - + Confirm invite Meghívás megerősítése - + Do you really want to invite %1 (%2)? Biztos, hogy meg akarod hívni a következő felhasználót: %1 (%2)? @@ -227,12 +227,12 @@ Kitiltás feloldva a felhasználónak: %1 - + Do you really want to start a private chat with %1? Biztosan privát csevegést akarsz indítani %1 felhasználóval? - + Cache migration failed! Gyorsítótár migráció nem sikerült! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Titkos tároló feloldása @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Bezárás + + InputBar @@ -910,7 +948,7 @@ Példa: https://szerver.em:8787 Műveletek - + &Copy @@ -1251,7 +1289,7 @@ Példa: https://szerver.em:8787 RoomInfo - + no version stored nincs tárolva verzió @@ -1268,6 +1306,16 @@ Példa: https://szerver.em:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1383,7 +1431,7 @@ Példa: https://szerver.em:8787 %1 tag - + SETTINGS BEÁLLÍTÁSOK @@ -1439,21 +1487,22 @@ Példa: https://szerver.em:8787 A titkosítás jelenleg kísérleti stádiumú és váratlan furcsaságok történhetnek.<br>Kérlek, vedd vigyelembe, hogy ha egyszer aktiváltad, nem lehet utána kikapcsolni. - - Respond to key requests - Válasz kulcskérelmekre + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. - Válaszoljon-e kérés esetén a kliens munkamenetkulcsokkal automatikusan. -Óvatosan használandó, mivel ez csak egy átmeneti megoldás a végponttól -végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközhitelesítés. + + Change + - + + Change what packs are enabled, remove packs or create new ones + + + + INFO INFÓ @@ -1468,7 +1517,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh Szoba verziója - + Failed to enable encryption: %1 Nem sikerült a titkosítás aktiválása: %1 @@ -1623,7 +1672,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineModel - + Message redaction failed: %1 Az üzenet visszavonása nem sikerült: %1 @@ -1767,12 +1816,12 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh %1 visszavonta a kopogását. - + You joined this room. Csatlakoztál ehhez a szobához. - + %1 has changed their avatar and changed their display name to %2. @@ -1842,7 +1891,7 @@ végpontig (E2E) titkosítás tesztelésére, amíg be nincs fejezve az eszközh TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Nem található titkosított privát csevegés ezzel a felhasználóval. Hozz létre egy titkosított privát csevegést vele, és próbáld újra! @@ -2478,7 +2527,7 @@ Ettől általában animálttá válik az alkalmazásablakok listáján szereplő descriptiveTime - + Yesterday Tegnap @@ -2662,7 +2711,7 @@ Média mérete: %2 message-description sent: - + You sent an audio clip Küldtél egy hangfájlt diff --git a/resources/langs/nheko_it.ts b/resources/langs/nheko_it.ts index 6056b31a..5a1d45f8 100644 --- a/resources/langs/nheko_it.ts +++ b/resources/langs/nheko_it.ts @@ -157,12 +157,12 @@ - + Confirm invite Conferma Invito - + Do you really want to invite %1 (%2)? Vuoi davvero inviare %1 (%2)? @@ -227,12 +227,12 @@ Rimosso il ban dall'utente: %1 - + Do you really want to start a private chat with %1? Sei sicuro di voler avviare una chat privata con %1? - + Cache migration failed! Migrazione della cache fallita! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Decifra i segreti @@ -604,6 +604,44 @@ Inoltra Messaggio + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Chiudi + + InputBar @@ -910,7 +948,7 @@ Esempio: https://server.mio:8787 Opzioni - + &Copy @@ -1252,7 +1290,7 @@ Verificare %1 adesso? RoomInfo - + no version stored nessuna versione memorizzata @@ -1269,6 +1307,16 @@ Verificare %1 adesso? Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1385,7 +1433,7 @@ Verificare %1 adesso? - + SETTINGS @@ -1441,19 +1489,22 @@ Verificare %1 adesso? - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1468,7 +1519,7 @@ Verificare %1 adesso? - + Failed to enable encryption: %1 Impossibile abilitare la crittografia: %1 @@ -1623,7 +1674,7 @@ Verificare %1 adesso? TimelineModel - + Message redaction failed: %1 Oscuramento del messaggio fallito: %1 @@ -1768,12 +1819,12 @@ Verificare %1 adesso? %1 ha oscurato la sua bussata. - + You joined this room. Sei entrato in questa stanza. - + %1 has changed their avatar and changed their display name to %2. @@ -1843,7 +1894,7 @@ Verificare %1 adesso? TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2467,7 +2518,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2651,7 +2702,7 @@ Peso media: %2 message-description sent: - + You sent an audio clip Hai inviato una clip audio diff --git a/resources/langs/nheko_ja.ts b/resources/langs/nheko_ja.ts index a8c18795..1ec862b0 100644 --- a/resources/langs/nheko_ja.ts +++ b/resources/langs/nheko_ja.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 永久追放を解除されたユーザー: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + 閉じる + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 オプション - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored バージョンが保存されていません @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1379,7 +1427,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1435,19 +1483,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1462,7 +1513,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 暗号化を有効にできませんでした: %1 @@ -1617,7 +1668,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 メッセージを編集できませんでした: %1 @@ -1761,12 +1812,12 @@ Example: https://server.my:8787 %1がノックを編集しました。 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1836,7 +1887,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2460,7 +2511,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday 昨日 @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip 音声データを送信しました diff --git a/resources/langs/nheko_ml.ts b/resources/langs/nheko_ml.ts index aeb704eb..011107c2 100644 --- a/resources/langs/nheko_ml.ts +++ b/resources/langs/nheko_ml.ts @@ -157,12 +157,12 @@ - + Confirm invite ക്ഷണം ഉറപ്പാക്കു - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + അടയ്‌ക്കുക + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. നിങ്ങൾ ഈ മുറിയിൽ ചേർന്നു. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_nl.ts b/resources/langs/nheko_nl.ts index ba3ceec1..b21db075 100644 --- a/resources/langs/nheko_nl.ts +++ b/resources/langs/nheko_nl.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. Je bent lid geworden van deze kamer. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2646,7 +2697,7 @@ Mediagrootte: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_pl.ts b/resources/langs/nheko_pl.ts index 4b6c31f2..72e4e771 100644 --- a/resources/langs/nheko_pl.ts +++ b/resources/langs/nheko_pl.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? Czy na pewno chcesz zaprosić %1 (%2)? @@ -227,12 +227,12 @@ Odblokowano użytkownika: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nie udało się przenieść pamięci podręcznej! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Zamknij + + InputBar @@ -908,7 +946,7 @@ Example: https://server.my:8787 - + &Copy @@ -1249,7 +1287,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1266,6 +1304,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1383,7 +1431,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1439,19 +1487,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1466,7 +1517,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 Nie udało się włączyć szyfrowania: %1 @@ -1621,7 +1672,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Redagowanie wiadomości nie powiodło się: %1 @@ -1767,12 +1818,12 @@ Example: https://server.my:8787 - + You joined this room. Dołączyłeś(-łaś) do tego pokoju. - + %1 has changed their avatar and changed their display name to %2. @@ -1842,7 +1893,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2466,7 +2517,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2650,7 +2701,7 @@ Rozmiar multimediów: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_pt_BR.ts b/resources/langs/nheko_pt_BR.ts index 7774ba58..b83585fb 100644 --- a/resources/langs/nheko_pt_BR.ts +++ b/resources/langs/nheko_pt_BR.ts @@ -157,12 +157,12 @@ - + Confirm invite Confirmar convite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Usuário desbanido: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Migração do cache falhou! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. Você entrou nessa sala. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_pt_PT.ts b/resources/langs/nheko_pt_PT.ts index cafbcdd6..a0a8c8a8 100644 --- a/resources/langs/nheko_pt_PT.ts +++ b/resources/langs/nheko_pt_PT.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_ro.ts b/resources/langs/nheko_ro.ts index 8ff28d24..6ea496f1 100644 --- a/resources/langs/nheko_ro.ts +++ b/resources/langs/nheko_ro.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ Utilizator dezinterzis: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Nu s-a putut migra cache-ul! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Închide + + InputBar @@ -910,7 +948,7 @@ Exemplu: https://serverul.meu:8787 Opțiuni - + &Copy @@ -1251,7 +1289,7 @@ Exemplu: https://serverul.meu:8787 RoomInfo - + no version stored nicio versiune stocată @@ -1268,6 +1306,16 @@ Exemplu: https://serverul.meu:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1385,7 +1433,7 @@ Exemplu: https://serverul.meu:8787 - + SETTINGS @@ -1441,19 +1489,22 @@ Exemplu: https://serverul.meu:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1468,7 +1519,7 @@ Exemplu: https://serverul.meu:8787 - + Failed to enable encryption: %1 Nu s-a putut activa criptarea: %1 @@ -1623,7 +1674,7 @@ Exemplu: https://serverul.meu:8787 TimelineModel - + Message redaction failed: %1 Redactare mesaj eșuată: %1 @@ -1769,12 +1820,12 @@ Exemplu: https://serverul.meu:8787 %1 și-a redactat ciocănitul. - + You joined this room. Te-ai alăturat camerei. - + %1 has changed their avatar and changed their display name to %2. @@ -1844,7 +1895,7 @@ Exemplu: https://serverul.meu:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2468,7 +2519,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Ieri @@ -2652,7 +2703,7 @@ Dimensiune media: %2 message-description sent: - + You sent an audio clip Ai trimis un clip audio diff --git a/resources/langs/nheko_ru.ts b/resources/langs/nheko_ru.ts index be97413f..67a306f2 100644 --- a/resources/langs/nheko_ru.ts +++ b/resources/langs/nheko_ru.ts @@ -157,12 +157,12 @@ - + Confirm invite Подтвердите приглашение - + Do you really want to invite %1 (%2)? Вы точно хотите пригласить %1 (%2)? @@ -227,12 +227,12 @@ Разблокированный пользователь: %1 - + Do you really want to start a private chat with %1? Вы действительно хотите начать личную переписку с %1? - + Cache migration failed! Миграция кэша не удалась! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Расшифровать секреты @@ -604,6 +604,44 @@ Переслать Сообщение + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Закрыть + + InputBar @@ -910,7 +948,7 @@ Example: https://server.my:8787 Опции - + &Copy @@ -1251,7 +1289,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored нет сохраненной версии @@ -1268,6 +1306,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1385,7 +1433,7 @@ Example: https://server.my:8787 %1 участник(ов) - + SETTINGS НАЙСТРОЙКИ @@ -1441,19 +1489,22 @@ Example: https://server.my:8787 - - Respond to key requests - Отвечать на запрос ключей + + Sticker & Emote Settings + - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO ИНФОРМАЦИЯ @@ -1468,7 +1519,7 @@ Example: https://server.my:8787 Версия Комнаты - + Failed to enable encryption: %1 Не удалось включить шифрование: %1 @@ -1623,7 +1674,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Ошибка редактирования сообщения: %1 @@ -1769,12 +1820,12 @@ Example: https://server.my:8787 %1 отредактировал его "стук". - + You joined this room. Вы присоединились к этой комнате. - + %1 has changed their avatar and changed their display name to %2. @@ -1844,7 +1895,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Не найдено личного чата с этим пользователем. Создайте зашифрованный личный чат с этим пользователем и попытайтесь еще раз. @@ -2474,7 +2525,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday Вчера @@ -2658,7 +2709,7 @@ Media size: %2 message-description sent: - + You sent an audio clip Вы отправили аудио запись diff --git a/resources/langs/nheko_si.ts b/resources/langs/nheko_si.ts index 67f09710..cf425990 100644 --- a/resources/langs/nheko_si.ts +++ b/resources/langs/nheko_si.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ - + Do you really want to start a private chat with %1? - + Cache migration failed! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1380,7 +1428,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1436,19 +1484,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1463,7 +1514,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 @@ -1618,7 +1669,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 @@ -1763,12 +1814,12 @@ Example: https://server.my:8787 - + You joined this room. - + %1 has changed their avatar and changed their display name to %2. @@ -1838,7 +1889,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2462,7 +2513,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip diff --git a/resources/langs/nheko_sv.ts b/resources/langs/nheko_sv.ts index fb662292..25db1c4b 100644 --- a/resources/langs/nheko_sv.ts +++ b/resources/langs/nheko_sv.ts @@ -157,12 +157,12 @@ - + Confirm invite Bekräfta inbjudan - + Do you really want to invite %1 (%2)? Är du säker på att du vill bjuda in %1 (%2)? @@ -227,12 +227,12 @@ Hävde bannlysningen av användare: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! Cache-migration misslyckades! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets Dekryptera hemliga nycklar @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + Stäng + + InputBar @@ -910,7 +948,7 @@ Exempel: https://server.my:8787 Alternativ - + &Copy @@ -1251,7 +1289,7 @@ Exempel: https://server.my:8787 RoomInfo - + no version stored ingen version lagrad @@ -1268,6 +1306,16 @@ Exempel: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1384,7 +1432,7 @@ Exempel: https://server.my:8787 - + SETTINGS @@ -1440,19 +1488,22 @@ Exempel: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1467,7 +1518,7 @@ Exempel: https://server.my:8787 - + Failed to enable encryption: %1 Kunde inte aktivera kryptering: %1 @@ -1622,7 +1673,7 @@ Exempel: https://server.my:8787 TimelineModel - + Message redaction failed: %1 Kunde inte maskera meddelande: %1 @@ -1767,12 +1818,12 @@ Exempel: https://server.my:8787 %1 maskerade sin knackning. - + You joined this room. Du gick med i detta rum. - + %1 has changed their avatar and changed their display name to %2. @@ -1842,7 +1893,7 @@ Exempel: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. Ingen krypterad privat chatt med denna användare kunde hittas. Skapa en krypterad privat chatt med användaren och försök igen. @@ -2474,7 +2525,7 @@ Detta gör vanligtvis att ikonen i aktivitetsfältet animeras på något sätt.< descriptiveTime - + Yesterday Igår @@ -2658,7 +2709,7 @@ Mediastorlek: %2 message-description sent: - + You sent an audio clip Du skickade ett ljudklipp diff --git a/resources/langs/nheko_zh_CN.ts b/resources/langs/nheko_zh_CN.ts index c84e3c82..75e9db95 100644 --- a/resources/langs/nheko_zh_CN.ts +++ b/resources/langs/nheko_zh_CN.ts @@ -157,12 +157,12 @@ - + Confirm invite - + Do you really want to invite %1 (%2)? @@ -227,12 +227,12 @@ 解禁用户: %1 - + Do you really want to start a private chat with %1? - + Cache migration failed! 缓存迁移失败! @@ -352,7 +352,7 @@ CrossSigningSecrets - + Decrypt secrets @@ -604,6 +604,44 @@ + + ImagePackSettingsDialog + + + Image pack settings + + + + + Private pack + + + + + Pack from this room + + + + + Globally enabled pack + + + + + Enable globally + + + + + Enables this pack to be used in all rooms + + + + + Close + + + InputBar @@ -906,7 +944,7 @@ Example: https://server.my:8787 - + &Copy @@ -1247,7 +1285,7 @@ Example: https://server.my:8787 RoomInfo - + no version stored @@ -1264,6 +1302,16 @@ Example: https://server.my:8787 Enter the tag you want to use: + + + Leave Room + + + + + Are you sure you want to leave this room? + + Leave room @@ -1379,7 +1427,7 @@ Example: https://server.my:8787 - + SETTINGS @@ -1435,19 +1483,22 @@ Example: https://server.my:8787 - - Respond to key requests + + Sticker & Emote Settings - - Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the - E2E implementation until device verification is completed. + + Change - + + Change what packs are enabled, remove packs or create new ones + + + + INFO @@ -1462,7 +1513,7 @@ Example: https://server.my:8787 - + Failed to enable encryption: %1 启用加密失败:%1 @@ -1617,7 +1668,7 @@ Example: https://server.my:8787 TimelineModel - + Message redaction failed: %1 删除消息失败:%1 @@ -1761,12 +1812,12 @@ Example: https://server.my:8787 - + You joined this room. 您已加入此房间 - + %1 has changed their avatar and changed their display name to %2. @@ -1836,7 +1887,7 @@ Example: https://server.my:8787 TimelineViewManager - + No encrypted private chat found with this user. Create an encrypted private chat with this user and try again. @@ -2460,7 +2511,7 @@ This usually causes the application icon in the task bar to animate in some fash descriptiveTime - + Yesterday @@ -2644,7 +2695,7 @@ Media size: %2 message-description sent: - + You sent an audio clip -- cgit 1.5.1 From f4e670d8d522eafa96bddff39baa1636fa83dc57 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jul 2021 12:13:24 +0200 Subject: Fix SSSS unlock without a password set fixes #657 --- src/ChatPage.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 615a15b3..a76756ae 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1028,8 +1028,15 @@ ChatPage::decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescriptio auto decryptionKey = mtx::crypto::key_from_recoverykey(text.toStdString(), keyDesc); - if (!decryptionKey) - decryptionKey = mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc); + if (!decryptionKey && keyDesc.passphrase) { + try { + decryptionKey = + mtx::crypto::key_from_passphrase(text.toStdString(), keyDesc); + } catch (std::exception &e) { + nhlog::crypto()->error("Failed to derive secret key from passphrase: {}", + e.what()); + } + } if (!decryptionKey) { QMessageBox::information( -- cgit 1.5.1 From 84446832719bb8775bb8160fe1101843933a3320 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jul 2021 13:51:21 +0200 Subject: Don't send encrypted olm messages to ourselves --- src/Olm.cpp | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 69503e6e..db4d771f 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -212,6 +212,11 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey nhlog::crypto()->info("sender : {}", msg.sender); nhlog::crypto()->info("sender_key: {}", msg.sender_key); + if (msg.sender_key == olm::client()->identity_keys().ed25519) { + nhlog::crypto()->warn("Ignoring olm message from ourselves!"); + return; + } + const auto my_key = olm::client()->identity_keys().curve25519; bool failed_decryption = false; @@ -1089,6 +1094,8 @@ send_encrypted_to_device_messages(const std::map> pks; + auto our_curve = olm::client()->identity_keys().curve25519; + for (const auto &[user, devices] : targets) { auto deviceKeys = cache::client()->userKeys(user); @@ -1122,8 +1129,14 @@ send_encrypted_to_device_messages(const std::mapwarn("Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + + auto session = cache::getLatestOlmSession(device_curve); if (!session || force_new_session) { claims.one_time_keys[user][device] = mtx::crypto::SIGNED_CURVE25519; pks[user][device].ed25519 = d.keys.at("ed25519:" + device); @@ -1137,7 +1150,7 @@ send_encrypted_to_device_messages(const std::map(); try { @@ -1256,8 +1269,8 @@ send_encrypted_to_device_messages(const std::mapquery_keys( req, - [ev_json, BindPks](const mtx::responses::QueryKeys &res, - mtx::http::RequestErr err) { + [ev_json, BindPks, our_curve](const mtx::responses::QueryKeys &res, + mtx::http::RequestErr err) { if (err) { nhlog::net()->warn("failed to query device keys: {} {}", err->matrix_error.error, @@ -1299,6 +1312,13 @@ send_encrypted_to_device_messages(const std::mapwarn( + "Skipping our own device, since sending " + "ourselves olm messages makes no sense."); + continue; + } + try { if (!mtx::crypto::verify_identity_signature( dev.second, device_id, user_id)) { -- cgit 1.5.1 From 8b3d0c14d0156649b71b403fdc5f0f995328e707 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jul 2021 14:10:38 +0200 Subject: Properly verify OTK signature --- src/Olm.cpp | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index db4d771f..338a3925 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1208,22 +1208,40 @@ send_encrypted_to_device_messages(const std::mapat("key"); - auto id_key = pks.at(user_id).at(device_id).curve25519; + auto sign_key = pks.at(user_id).at(device_id).ed25519; + auto id_key = pks.at(user_id).at(device_id).curve25519; + + // Verify signature + { + auto signedKey = *rd.second.begin(); + std::string signature = + signedKey["signatures"][user_id].value( + "ed25519:" + device_id, ""); + + if (signature.empty() || + !mtx::crypto::ed25519_verify_signature( + sign_key, signedKey, signature)) { + nhlog::net()->warn( + "Skipping device {} as its one time key " + "has an invalid signature.", + device_id); + continue; + } + } + auto session = olm::client()->create_outbound_session(id_key, otk); messages[mtx::identifiers::parse( user_id)][device_id] = olm::client() - ->create_olm_encrypted_content( - session.get(), - ev_json, - UserId(user_id), - pks.at(user_id).at(device_id).ed25519, - id_key) + ->create_olm_encrypted_content(session.get(), + ev_json, + UserId(user_id), + sign_key, + id_key) .get(); try { -- cgit 1.5.1 From 80b9d4528ebc184dc6ff657c858b69cb88245934 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jul 2021 15:39:49 +0200 Subject: Remove SSSS requests on the UI thread might fix #656 --- src/Olm.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 338a3925..81121443 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1406,9 +1406,12 @@ request_cross_signing_keys() body, [request_id = secretRequest.request_id, secretName](mtx::http::RequestErr err) { if (err) { - request_id_to_secret_name.erase(request_id); nhlog::net()->error("Failed to send request for secrect '{}'", secretName); + // Cancel request on UI thread + QTimer::singleShot(1, cache::client(), [request_id]() { + request_id_to_secret_name.erase(request_id); + }); return; } }); -- cgit 1.5.1 From 570d00b000bd558592af317746fa3639fb022fa4 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 25 Jul 2021 17:07:10 +0200 Subject: Fix crash when storing secrets Nested QEventLoops are scary. Ultimately we shouldn't use them, but I have no better solution right now. fixes #656 --- src/Cache.cpp | 45 +++++++++++++++++++++++---------------------- src/Cache_p.h | 6 +++--- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index d651b182..4c24a712 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -715,32 +715,29 @@ Cache::restoreOlmAccount() } void -Cache::storeSecret(const std::string &name, const std::string &secret) +Cache::storeSecret(const std::string name, const std::string secret) { auto settings = UserSettings::instance(); - QKeychain::WritePasswordJob job(QCoreApplication::applicationName()); - job.setAutoDelete(false); - job.setInsecureFallback(true); - job.setKey("matrix." + - QString(QCryptographicHash::hash(settings->profile().toUtf8(), - QCryptographicHash::Sha256)) + - "." + name.c_str()); - job.setTextData(QString::fromStdString(secret)); - QEventLoop loop; - job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); - job.start(); - loop.exec(); - - if (job.error()) { - nhlog::db()->warn( - "Storing secret '{}' failed: {}", name, job.errorString().toStdString()); - } else { - emit secretChanged(name); - } + auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName()); + job->setInsecureFallback(true); + job->setKey("matrix." + + QString(QCryptographicHash::hash(settings->profile().toUtf8(), + QCryptographicHash::Sha256)) + + "." + name.c_str()); + job->setTextData(QString::fromStdString(secret)); + QObject::connect(job, &QKeychain::Job::finished, job, [name, this](QKeychain::Job *job) { + if (job->error()) { + nhlog::db()->warn( + "Storing secret '{}' failed: {}", name, job->errorString().toStdString()); + } else { + emit secretChanged(name); + } + }); + job->start(); } void -Cache::deleteSecret(const std::string &name) +Cache::deleteSecret(const std::string name) { auto settings = UserSettings::instance(); QKeychain::DeletePasswordJob job(QCoreApplication::applicationName()); @@ -750,6 +747,8 @@ Cache::deleteSecret(const std::string &name) QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)) + "." + name.c_str()); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! QEventLoop loop; job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); job.start(); @@ -759,7 +758,7 @@ Cache::deleteSecret(const std::string &name) } std::optional -Cache::secret(const std::string &name) +Cache::secret(const std::string name) { auto settings = UserSettings::instance(); QKeychain::ReadPasswordJob job(QCoreApplication::applicationName()); @@ -769,6 +768,8 @@ Cache::secret(const std::string &name) QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)) + "." + name.c_str()); + // FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean + // time! QEventLoop loop; job.connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit); job.start(); diff --git a/src/Cache_p.h b/src/Cache_p.h index c9d42202..89c88925 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -285,9 +285,9 @@ public: void saveOlmAccount(const std::string &pickled); std::string restoreOlmAccount(); - void storeSecret(const std::string &name, const std::string &secret); - void deleteSecret(const std::string &name); - std::optional secret(const std::string &name); + void storeSecret(const std::string name, const std::string secret); + void deleteSecret(const std::string name); + std::optional secret(const std::string name); template static constexpr bool isStateEvent(const mtx::events::StateEvent &) -- cgit 1.5.1 From c8879e7c076bd865e2e0f19638b1a0f6fe943f0c Mon Sep 17 00:00:00 2001 From: Joe Date: Sun, 25 Jul 2021 19:03:56 -0400 Subject: Remove QML from format script (it causes too many issues) --- .ci/format.sh | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/.ci/format.sh b/.ci/format.sh index 2d922ee1..cc4a3b82 100755 --- a/.ci/format.sh +++ b/.ci/format.sh @@ -14,17 +14,4 @@ do clang-format -i "$f" done; -QMLFORMAT_PATH=$(command -v qmlformat || true) - -if [ -n "$QMLFORMAT_PATH" ]; then - QML_FILES=$(find resources -type f -iname "*.qml") - - for f in $QML_FILES - do - $QMLFORMAT_PATH -i "$f" - done; -else - echo "qmlformat not found; skipping qml formatting" -fi - git diff --exit-code -- cgit 1.5.1 From e8e48305c8322c58cef88bf8c23d7972d941d7f4 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 26 Jul 2021 13:55:51 +0200 Subject: Remove version checks for unsupported Qt versions --- src/notifications/ManagerLinux.cpp | 5 +---- src/ui/InfoMessage.cpp | 15 ++------------- src/ui/Painter.h | 5 ----- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 598b2bd0..2809de87 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -295,12 +295,9 @@ operator<<(QDBusArgument &arg, const QImage &image) int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3); arg << i.depth() / channels; arg << channels; -#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - arg << QByteArray(reinterpret_cast(i.bits()), i.byteCount()); -#else arg << QByteArray(reinterpret_cast(i.bits()), i.sizeInBytes()); -#endif arg.endStructure(); + return arg; } diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp index fb3b306a..ebe0e63f 100644 --- a/src/ui/InfoMessage.cpp +++ b/src/ui/InfoMessage.cpp @@ -29,13 +29,7 @@ InfoMessage::InfoMessage(QString msg, QWidget *parent) initFont(); QFontMetrics fm{font()}; -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) - // width deprecated in 5.13 - width_ = fm.width(msg_) + HPadding * 2; -#else - width_ = fm.horizontalAdvance(msg_) + HPadding * 2; -#endif - + width_ = fm.horizontalAdvance(msg_) + HPadding * 2; height_ = fm.ascent() + 2 * VPadding; setFixedHeight(height_ + 2 * HMargin); @@ -77,12 +71,7 @@ DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent) msg_ = datetime.date().toString(fmt); QFontMetrics fm{font()}; -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) - // width deprecated in 5.13 - width_ = fm.width(msg_) + HPadding * 2; -#else - width_ = fm.horizontalAdvance(msg_) + HPadding * 2; -#endif + width_ = fm.horizontalAdvance(msg_) + HPadding * 2; height_ = fm.ascent() + 2 * VPadding; setFixedHeight(height_ + 2 * HMargin); diff --git a/src/ui/Painter.h b/src/ui/Painter.h index 3353f0c7..9f974116 100644 --- a/src/ui/Painter.h +++ b/src/ui/Painter.h @@ -27,12 +27,7 @@ public: { QFontMetrics m(fontMetrics()); if (textWidth < 0) { -#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) - // deprecated in 5.13: - textWidth = m.width(text); -#else textWidth = m.horizontalAdvance(text); -#endif } drawText((outerw - x - textWidth), y + m.ascent(), text); } -- cgit 1.5.1 From 6222ae88ce08c289ad67058dd3200bd1d5950072 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 26 Jul 2021 18:57:46 +0200 Subject: Fix crash on invalid utf8 in decrypted events --- src/Olm.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index 81121443..d20bf9a4 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1048,24 +1048,24 @@ decryptEvent(const MegolmSessionIndex &index, return {DecryptionErrorCode::DecryptionFailed, e.what(), std::nullopt}; } - // Add missing fields for the event. - json body = json::parse(msg_str); - body["event_id"] = event.event_id; - body["sender"] = event.sender; - body["origin_server_ts"] = event.origin_server_ts; - body["unsigned"] = event.unsigned_data; + try { + // Add missing fields for the event. + json body = json::parse(msg_str); + body["event_id"] = event.event_id; + body["sender"] = event.sender; + body["origin_server_ts"] = event.origin_server_ts; + body["unsigned"] = event.unsigned_data; - // relations are unencrypted in content... - mtx::common::add_relations(body["content"], event.content.relations); + // relations are unencrypted in content... + mtx::common::add_relations(body["content"], event.content.relations); - mtx::events::collections::TimelineEvent te; - try { + mtx::events::collections::TimelineEvent te; mtx::events::collections::from_json(body, te); + + return {std::nullopt, std::nullopt, std::move(te.data)}; } catch (std::exception &e) { return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; } - - return {std::nullopt, std::nullopt, std::move(te.data)}; } crypto::Trust -- cgit 1.5.1 From d955444dc18b2977d8b22e87b92c85bca90a623e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 27 Jul 2021 22:35:38 +0200 Subject: Port to explicit connect syntax Also fix a lot of warnings. --- resources/qml/ChatPage.qml | 1 + resources/qml/ForwardCompleter.qml | 6 ++-- resources/qml/MatrixText.qml | 4 +-- resources/qml/MessageInput.qml | 35 ++++++++++++++----- resources/qml/MessageView.qml | 26 ++++++++++---- resources/qml/PrivacyScreen.qml | 5 +-- resources/qml/QuickSwitcher.qml | 6 ++-- resources/qml/RoomList.qml | 7 ++-- resources/qml/Root.qml | 43 +++++++++++++----------- resources/qml/UserProfile.qml | 5 +-- resources/qml/delegates/PlayableMediaMessage.qml | 5 +-- resources/qml/voip/CallInvite.qml | 5 +-- src/Logging.cpp | 10 +----- 13 files changed, 96 insertions(+), 62 deletions(-) diff --git a/resources/qml/ChatPage.qml b/resources/qml/ChatPage.qml index ae37187b..e56d7d46 100644 --- a/resources/qml/ChatPage.qml +++ b/resources/qml/ChatPage.qml @@ -56,6 +56,7 @@ Rectangle { RoomList { id: roomlist + implicitHeight: chatPage.height collapsed: parent.collapsed } diff --git a/resources/qml/ForwardCompleter.qml b/resources/qml/ForwardCompleter.qml index 127b59c2..525477cd 100644 --- a/resources/qml/ForwardCompleter.qml +++ b/resources/qml/ForwardCompleter.qml @@ -109,15 +109,17 @@ Popup { } Connections { - onCompletionSelected: { + function onCompletionSelected(id) { room.forwardMessage(messageContextMenu.eventId, id); forwardMessagePopup.close(); } - onCountChanged: { + + function onCountChanged() { if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) completerPopup.currentIndex = 0; } + target: completerPopup } diff --git a/resources/qml/MatrixText.qml b/resources/qml/MatrixText.qml index 35e5f7e7..7253cbe6 100644 --- a/resources/qml/MatrixText.qml +++ b/resources/qml/MatrixText.qml @@ -18,8 +18,8 @@ TextEdit { //enabled: selectByMouse color: Nheko.colors.text onLinkActivated: Nheko.openLink(link) - ToolTip.visible: hoveredLink - ToolTip.text: hoveredLink + ToolTip.visible: hoveredLink || false + ToolTip.text: hoveredLink || "" Component.onCompleted: { TimelineManager.fixImageRendering(r.textDocument, r); } diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 58d71a4e..8bc8ac62 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -261,18 +261,24 @@ Rectangle { background: null Connections { - onRoomChanged: { + function onRoomChanged() { messageInput.clear(); - messageInput.append(room.input.text()); + if (room) + messageInput.append(room.input.text()); + messageInput.completerTriggeredAt = -1; popup.completerName = ""; messageInput.forceActiveFocus(); } + target: timelineView } Connections { - onCompletionClicked: messageInput.insertCompletion(completion) + function onCompletionClicked(completion) { + messageInput.insertCompletion(completion); + } + target: popup } @@ -284,28 +290,39 @@ Rectangle { } Connections { - ignoreUnknownSignals: true - onInsertText: { + function onInsertText(text) { messageInput.remove(messageInput.selectionStart, messageInput.selectionEnd); messageInput.insert(messageInput.cursorPosition, text); } - onTextChanged: { + + function onTextChanged(newText) { messageInput.text = newText; messageInput.cursorPosition = newText.length; } + + ignoreUnknownSignals: true target: room ? room.input : null } Connections { + function onReplyChanged() { + messageInput.forceActiveFocus(); + } + + function onEditChanged() { + messageInput.forceActiveFocus(); + } + ignoreUnknownSignals: true - onReplyChanged: messageInput.forceActiveFocus() - onEditChanged: messageInput.forceActiveFocus() target: room } Connections { + function onFocusInput() { + messageInput.forceActiveFocus(); + } + target: TimelineManager - onFocusInput: messageInput.forceActiveFocus() } MouseArea { diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 50cbd371..07feec8c 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -200,15 +200,22 @@ ScrollView { } Connections { + function onFocusChanged() { + readTimer.running = TimelineManager.isWindowFocused; + } + target: TimelineManager - onFocusChanged: readTimer.running = TimelineManager.isWindowFocused } Timer { id: readTimer // force current read index to update - onTriggered: chat.model.setCurrentIndex(chat.model.currentIndex) + onTriggered: { + if (chat.model) { + chat.model.setCurrentIndex(chat.model.currentIndex); + } + } interval: 1000 } @@ -265,11 +272,15 @@ ScrollView { } Connections { - target: chat.model - onRoomAvatarUrlChanged: { + function onRoomAvatarUrlChanged() { messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/"); } - onScrollToIndex: chat.positionViewAtIndex(index, ListView.Visible) + + function onScrollToIndex(index) { + chat.positionViewAtIndex(index, ListView.Visible); + } + + target: chat.model } Label { @@ -467,12 +478,13 @@ ScrollView { } Connections { - target: chat - onMovementEnded: { + function onMovementEnded() { if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height) chat.model.currentIndex = index; } + + target: chat } } diff --git a/resources/qml/PrivacyScreen.qml b/resources/qml/PrivacyScreen.qml index ca73cfd7..5f18a1ce 100644 --- a/resources/qml/PrivacyScreen.qml +++ b/resources/qml/PrivacyScreen.qml @@ -13,8 +13,7 @@ Item { property int screenTimeout Connections { - target: TimelineManager - onFocusChanged: { + function onFocusChanged() { if (TimelineManager.isWindowFocused) { screenSaverTimer.stop(); screenSaver.state = "Invisible"; @@ -24,6 +23,8 @@ Item { } } + + target: TimelineManager } Timer { diff --git a/resources/qml/QuickSwitcher.qml b/resources/qml/QuickSwitcher.qml index 8c4f47ca..61155acf 100644 --- a/resources/qml/QuickSwitcher.qml +++ b/resources/qml/QuickSwitcher.qml @@ -71,15 +71,17 @@ Popup { } Connections { - onCompletionSelected: { + function onCompletionSelected(id) { Rooms.setCurrentRoom(id); quickSwitcher.close(); } - onCountChanged: { + + function onCountChanged() { if (completerPopup.count > 0 && (completerPopup.currentIndex < 0 || completerPopup.currentIndex >= completerPopup.count)) completerPopup.currentIndex = 0; } + target: completerPopup } diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 2be5fe92..a2e50fab 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -32,11 +32,12 @@ Page { } Connections { - onActiveTimelineChanged: { + function onCurrentRoomChanged() { roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain); console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId)); } - target: TimelineManager + + target: Rooms } Platform.Menu { @@ -66,7 +67,7 @@ Page { title: qsTr("Leave Room") text: qsTr("Are you sure you want to leave this room?") - modality: Qt.Modal + modality: Qt.ApplicationModal onAccepted: Rooms.leave(roomContextMenu.roomid) buttons: Dialog.Ok | Dialog.Cancel } diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 1793d9bc..e80ff764 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -116,53 +116,43 @@ Page { } Connections { - target: TimelineManager - onNewDeviceVerificationRequest: { + function onNewDeviceVerificationRequest(flow) { var dialog = deviceVerificationDialog.createObject(timelineRoot, { "flow": flow }); dialog.show(); } - onOpenProfile: { + + function onOpenProfile(profile) { var userProfile = userProfileComponent.createObject(timelineRoot, { "profile": profile }); userProfile.show(); } - onShowImagePackSettings: { + + function onShowImagePackSettings(packlist) { var packSet = packSettingsComponent.createObject(timelineRoot, { "packlist": packlist }); packSet.show(); } - } - Connections { - target: CallManager - onNewInviteState: { - if (CallManager.haveCallInvite && Settings.mobileMode) { - var dialog = mobileCallInviteDialog.createObject(msgView); - dialog.open(); - } - } - } - - Connections { - target: TimelineManager - onOpenRoomMembersDialog: { + function onOpenRoomMembersDialog(members) { var membersDialog = roomMembersComponent.createObject(timelineRoot, { "members": members, "roomName": Rooms.currentRoom.roomName }); membersDialog.show(); } - onOpenRoomSettingsDialog: { + + function onOpenRoomSettingsDialog(settings) { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { "roomSettings": settings }); roomSettings.show(); } - onOpenInviteUsersDialog: { + + function onOpenInviteUsersDialog(invitees) { var dialog = inviteDialog.createObject(timelineRoot, { "roomId": Rooms.currentRoom.roomId, "plainRoomName": Rooms.currentRoom.plainRoomName, @@ -170,6 +160,19 @@ Page { }); dialog.show(); } + + target: TimelineManager + } + + Connections { + function onNewInviteState() { + if (CallManager.haveCallInvite && Settings.mobileMode) { + var dialog = mobileCallInviteDialog.createObject(msgView); + dialog.open(); + } + } + + target: CallManager } ChatPage { diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 826d3165..d138060b 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -83,12 +83,13 @@ ApplicationWindow { } Connections { - target: profile - onDisplayError: { + function onDisplayError(errorMessage) { errorText.text = errorMessage; errorText.opacity = 1; hideErrorAnimation.restart(); } + + target: profile } TextInput { diff --git a/resources/qml/delegates/PlayableMediaMessage.qml b/resources/qml/delegates/PlayableMediaMessage.qml index fd764d52..73c74ec0 100644 --- a/resources/qml/delegates/PlayableMediaMessage.qml +++ b/resources/qml/delegates/PlayableMediaMessage.qml @@ -185,8 +185,7 @@ Rectangle { } Connections { - target: room - onMediaCached: { + function onMediaCached(mxcUrl, cacheUrl) { if (mxcUrl == url) { media.source = cacheUrl; button.state = "stopped"; @@ -194,6 +193,8 @@ Rectangle { } console.log("media cached: " + mxcUrl + " at " + cacheUrl); } + + target: room } } diff --git a/resources/qml/voip/CallInvite.qml b/resources/qml/voip/CallInvite.qml index 1b57976d..253fa25c 100644 --- a/resources/qml/voip/CallInvite.qml +++ b/resources/qml/voip/CallInvite.qml @@ -23,12 +23,13 @@ Popup { } Connections { - target: CallManager - onNewInviteState: { + function onNewInviteState() { if (!CallManager.haveCallInvite) close(); } + + target: CallManager } ColumnLayout { diff --git a/src/Logging.cpp b/src/Logging.cpp index 642e8957..67bcaf7a 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -30,19 +30,11 @@ qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QStri const char *function = context.function ? context.function : ""; if ( - // Surpress binding wrning for now, as we can't set restore mode to keep compat with - // qt 5.10 - msg.contains(QStringLiteral( - "QML Binding: Not restoring previous value because restoreMode has not been set.")) || // The default style has the point size set. If you use pixel size anywhere, you get // that warning, which is useless, since sometimes you need the pixel size to match the // text to the size of the outer element for example. This is done in the avatar and // without that you get one warning for every Avatar displayed, which is stupid! - msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size.")) || - // The new syntax breaks rebinding on Qt < 5.15. Until we can drop that, we still need it. - msg.endsWith(QStringLiteral( - "QML Connections: Implicitly defined onFoo properties in Connections are " - "deprecated. Use this syntax instead: function onFoo() { ... }"))) + msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size."))) return; switch (type) { -- cgit 1.5.1 From 4dd994ae009b622cd35e292d1170a3f60a26c4d6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 18:11:33 -0400 Subject: QML the read receipts list There are probably a few things wrong with this, but I'm going to call it good enough for an initial commit --- CMakeLists.txt | 4 +- resources/qml/ReadReceipts.qml | 118 +++++++++++++++++++++++ resources/qml/Root.qml | 19 ++++ resources/qml/StatusIndicator.qml | 2 +- resources/res.qrc | 2 +- src/ChatPage.cpp | 1 - src/MainWindow.cpp | 22 ----- src/MainWindow.h | 1 - src/ReadReceiptsModel.cpp | 120 +++++++++++++++++++++++ src/ReadReceiptsModel.h | 86 +++++++++++++++++ src/dialogs/ReadReceipts.cpp | 179 ----------------------------------- src/dialogs/ReadReceipts.h | 61 ------------ src/timeline/TimelineModel.cpp | 5 +- src/timeline/TimelineModel.h | 4 +- src/timeline/TimelineViewManager.cpp | 7 ++ 15 files changed, 360 insertions(+), 271 deletions(-) create mode 100644 resources/qml/ReadReceipts.qml create mode 100644 src/ReadReceiptsModel.cpp create mode 100644 src/ReadReceiptsModel.h delete mode 100644 src/dialogs/ReadReceipts.cpp delete mode 100644 src/dialogs/ReadReceipts.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b26602c..e9371579 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,7 +286,6 @@ set(SRC_FILES src/dialogs/Logout.cpp src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp - src/dialogs/ReadReceipts.cpp # Emoji src/emoji/EmojiModel.cpp @@ -352,6 +351,7 @@ set(SRC_FILES src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp + src/ReadReceiptsModel.cpp src/RegisterPage.cpp src/SSOHandler.cpp src/CombinedImagePackModel.cpp @@ -499,7 +499,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/PreviewUploadOverlay.h src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h - src/dialogs/ReadReceipts.h # Emoji src/emoji/EmojiModel.h @@ -558,6 +557,7 @@ qt5_wrap_cpp(MOC_HEADERS src/MainWindow.h src/MemberList.h src/MxcImageProvider.h + src/ReadReceiptsModel.h src/RegisterPage.h src/SSOHandler.h src/CombinedImagePackModel.h diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml new file mode 100644 index 00000000..21b9b15e --- /dev/null +++ b/resources/qml/ReadReceipts.qml @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import im.nheko 1.0 + +ApplicationWindow { + id: readReceiptsRoot + + property ReadReceiptsModel readReceipts + + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 380 + width: 340 + minimumHeight: 380 + minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium + palette: Nheko.colors + color: Nheko.colors.window + + ColumnLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + spacing: Nheko.paddingMedium + + Label { + id: headerTitle + + Layout.alignment: Qt.AlignCenter + text: qsTr("Read receipts") + font.pointSize: fontMetrics.font.pointSize * 1.5 + } + + ScrollView { + palette: Nheko.colors + padding: Nheko.paddingMedium + ScrollBar.horizontal.visible: false + Layout.fillHeight: true + Layout.minimumHeight: 200 + Layout.fillWidth: true + + ListView { + id: readReceiptsList + + clip: true + spacing: Nheko.paddingMedium + boundsBehavior: Flickable.StopAtBounds + model: readReceipts + + delegate: RowLayout { + spacing: Nheko.paddingMedium + + Avatar { + width: Nheko.avatarSize + height: Nheko.avatarSize + userid: model.mxid + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: model.displayName + onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + ToolTip.visible: avatarHover.hovered + ToolTip.text: model.mxid + + HoverHandler { + id: avatarHover + } + + } + + ColumnLayout { + spacing: Nheko.paddingSmall + + Label { + text: model.displayName + color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window) + font.pointSize: fontMetrics.font.pointSize + ToolTip.visible: displayNameHover.hovered + ToolTip.text: model.mxid + + TapHandler { + onSingleTapped: chat.model.openUserProfile(userId) + } + + CursorShape { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + } + + HoverHandler { + id: displayNameHover + } + + } + + Label { + text: model.timestamp + color: Nheko.colors.buttonText + font.pointSize: fontMetrics.font.pointSize * 0.9 + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + + } + + } + + } + + } + + } + +} diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index e80ff764..a099b5e6 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -96,6 +96,14 @@ Page { } + Component { + id: readReceiptsDialog + + ReadReceipts { + } + + } + Shortcut { sequence: "Ctrl+K" onActivated: { @@ -164,6 +172,17 @@ Page { target: TimelineManager } + Connections { + function onOpenReadReceiptsDialog() { + var dialog = readReceiptsDialog.createObject(timelineRoot, { + "readReceipts": rr + }); + dialog.show(); + } + + target: Rooms.currentRoom + } + Connections { function onNewInviteState() { if (CallManager.haveCallInvite && Settings.mobileMode) { diff --git a/resources/qml/StatusIndicator.qml b/resources/qml/StatusIndicator.qml index 7e471d69..0af02b3c 100644 --- a/resources/qml/StatusIndicator.qml +++ b/resources/qml/StatusIndicator.qml @@ -34,7 +34,7 @@ ImageButton { } onClicked: { if (status == MtxEvent.Read) - room.readReceiptsAction(eventId); + room.showReadReceipts(eventId); } image: { diff --git a/resources/res.qrc b/resources/res.qrc index 5d37c397..2b655b9e 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -112,7 +112,6 @@
qtquickcontrols2.conf - qml/Root.qml qml/ChatPage.qml qml/CommunitiesList.qml @@ -177,6 +176,7 @@ qml/components/FlatButton.qml qml/RoomMembers.qml qml/InviteDialog.qml + qml/ReadReceipts.qml media/ring.ogg diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index a76756ae..42e3bc7b 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -31,7 +31,6 @@ #include "notifications/Manager.h" -#include "dialogs/ReadReceipts.h" #include "timeline/TimelineViewManager.h" #include "blurhash.hpp" diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c0486d01..8bc90f29 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -36,7 +36,6 @@ #include "dialogs/JoinRoom.h" #include "dialogs/LeaveRoom.h" #include "dialogs/Logout.h" -#include "dialogs/ReadReceipts.h" MainWindow *MainWindow::instance_ = nullptr; @@ -398,27 +397,6 @@ MainWindow::openLogoutDialog() showDialog(dialog); } -void -MainWindow::openReadReceiptsDialog(const QString &event_id) -{ - auto dialog = new dialogs::ReadReceipts(this); - - const auto room_id = chat_page_->currentRoom(); - - try { - dialog->addUsers(cache::readReceipts(event_id, room_id)); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id.toStdString(), - chat_page_->currentRoom().toStdString()); - dialog->deleteLater(); - - return; - } - - showDialog(dialog); -} - bool MainWindow::hasActiveDialogs() const { diff --git a/src/MainWindow.h b/src/MainWindow.h index 6d62545c..d423af9f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -65,7 +65,6 @@ public: std::function callback); void openJoinRoomDialog(std::function callback); void openLogoutDialog(); - void openReadReceiptsDialog(const QString &event_id); void hideOverlay(); void showSolidOverlayModal(QWidget *content, diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp new file mode 100644 index 00000000..293733d3 --- /dev/null +++ b/src/ReadReceiptsModel.cpp @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ReadReceiptsModel.h" + +#include + +#include "Cache.h" +#include "Logging.h" +#include "Utils.h" + +ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject *parent) + : QAbstractListModel{parent} + , event_id_{event_id} + , room_id_{room_id} +{ + try { + addUsers(cache::readReceipts(event_id, room_id)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id.toStdString(), + room_id_.toStdString()); + + return; + } +} + +ReadReceiptsModel::~ReadReceiptsModel() +{ + for (const auto &item : readReceipts_) + item->deleteLater(); +} + +QHash +ReadReceiptsModel::roleNames() const +{ + return {{Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Timestamp, "timestamp"}}; +} + +QVariant +ReadReceiptsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0) + return {}; + + switch (role) { + case Mxid: + return readReceipts_[index.row()]->mxid(); + case DisplayName: + return readReceipts_[index.row()]->displayName(); + case AvatarUrl: + return readReceipts_[index.row()]->avatarUrl(); + case Timestamp: + // the uint64_t to QVariant conversion was ambiguous, so... + return readReceipts_[index.row()]->timestamp(); + default: + return {}; + } +} + +void +ReadReceiptsModel::addUsers( + const std::multimap> &users) +{ + std::multimap> unshown; + for (const auto &user : users) { + if (users_.find(user.first) == users_.end()) + unshown.emplace(user); + } + + beginInsertRows( + QModelIndex{}, readReceipts_.length(), readReceipts_.length() + unshown.size() - 1); + + for (const auto &user : unshown) + readReceipts_.push_back( + new ReadReceipt{QString::fromStdString(user.second), room_id_, user.first, this}); + + users_.merge(unshown); + + endInsertRows(); +} + +ReadReceipt::ReadReceipt(QString mxid, QString room_id, uint64_t timestamp, QObject *parent) + : QObject{parent} + , mxid_{mxid} + , room_id_{room_id} + , displayName_{cache::displayName(room_id_, mxid_)} + , avatarUrl_{cache::avatarUrl(room_id_, mxid_)} + , timestamp_{timestamp} +{} + +QString +ReadReceipt::timestamp() const +{ + return dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp_)); +} + +QString +ReadReceipt::dateFormat(const QDateTime &then) const +{ + auto now = QDateTime::currentDateTime(); + auto days = then.daysTo(now); + + if (days == 0) + return tr("Today %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + else if (days < 2) + return tr("Yesterday %1") + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + else if (days < 7) + return QString("%1 %2") + .arg(then.toString("dddd")) + .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + + return QLocale::system().toString(then.time(), QLocale::ShortFormat); +} diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h new file mode 100644 index 00000000..d90bf7c1 --- /dev/null +++ b/src/ReadReceiptsModel.h @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef READRECEIPTSMODEL_H +#define READRECEIPTSMODEL_H + +#include +#include +#include + +class ReadReceipt : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString mxid READ mxid CONSTANT) + Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY(QString timestamp READ timestamp CONSTANT) + +public: + explicit ReadReceipt(QString mxid, + QString room_id, + uint64_t timestamp, + QObject *parent = nullptr); + + QString mxid() const { return mxid_; } + QString displayName() const { return displayName_; } + QString avatarUrl() const { return avatarUrl_; } + QString timestamp() const; + +signals: + void displayNameChanged(); + void avatarUrlChanged(); + +private: + QString dateFormat(const QDateTime &then) const; + + QString mxid_; + QString room_id_; + QString displayName_; + QString avatarUrl_; + uint64_t timestamp_; +}; + +class ReadReceiptsModel : public QAbstractListModel +{ + Q_OBJECT + + Q_PROPERTY(QString eventId READ eventId CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + +public: + enum Roles + { + Mxid, + DisplayName, + AvatarUrl, + Timestamp, + }; + + explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); + ~ReadReceiptsModel() override; + + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent) const override + { + Q_UNUSED(parent) + return readReceipts_.size(); + } + QVariant data(const QModelIndex &index, int role) const override; + +public slots: + void addUsers(const std::multimap> &users); + +private: + QString event_id_; + QString room_id_; + QVector readReceipts_; + std::multimap> users_; +}; + +#endif // READRECEIPTSMODEL_H diff --git a/src/dialogs/ReadReceipts.cpp b/src/dialogs/ReadReceipts.cpp deleted file mode 100644 index fa7132fd..00000000 --- a/src/dialogs/ReadReceipts.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dialogs/ReadReceipts.h" - -#include "AvatarProvider.h" -#include "Cache.h" -#include "ChatPage.h" -#include "Config.h" -#include "Utils.h" -#include "ui/Avatar.h" - -using namespace dialogs; - -ReceiptItem::ReceiptItem(QWidget *parent, - const QString &user_id, - uint64_t timestamp, - const QString &room_id) - : QWidget(parent) -{ - topLayout_ = new QHBoxLayout(this); - topLayout_->setMargin(0); - - textLayout_ = new QVBoxLayout; - textLayout_->setMargin(0); - textLayout_->setSpacing(conf::modals::TEXT_SPACING); - - QFont nameFont; - nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1); - - auto displayName = cache::displayName(room_id, user_id); - - avatar_ = new Avatar(this, 44); - avatar_->setLetter(utils::firstChar(displayName)); - - // If it's a matrix id we use the second letter. - if (displayName.size() > 1 && displayName.at(0) == '@') - avatar_->setLetter(QChar(displayName.at(1))); - - userName_ = new QLabel(displayName, this); - userName_->setFont(nameFont); - - timestamp_ = new QLabel(dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp)), this); - - textLayout_->addWidget(userName_); - textLayout_->addWidget(timestamp_); - - topLayout_->addWidget(avatar_); - topLayout_->addLayout(textLayout_, 1); - - avatar_->setImage(ChatPage::instance()->currentRoom(), user_id); -} - -void -ReceiptItem::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -QString -ReceiptItem::dateFormat(const QDateTime &then) const -{ - auto now = QDateTime::currentDateTime(); - auto days = then.daysTo(now); - - if (days == 0) - return tr("Today %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - else if (days < 2) - return tr("Yesterday %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - else if (days < 7) - return QString("%1 %2") - .arg(then.toString("dddd")) - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); - - return QLocale::system().toString(then.time(), QLocale::ShortFormat); -} - -ReadReceipts::ReadReceipts(QWidget *parent) - : QFrame(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(conf::modals::WIDGET_SPACING); - layout->setMargin(conf::modals::WIDGET_MARGIN); - - userList_ = new QListWidget; - userList_->setFrameStyle(QFrame::NoFrame); - userList_->setSelectionMode(QAbstractItemView::NoSelection); - userList_->setSpacing(conf::modals::TEXT_SPACING); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); - - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - setMinimumHeight(userList_->sizeHint().height() * 2); - setMinimumWidth(std::max(userList_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN, - QFontMetrics(largeFont).averageCharWidth() * 30 - - 2 * conf::modals::WIDGET_MARGIN)); - - QFont font; - font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO); - - topLabel_ = new QLabel(tr("Read receipts"), this); - topLabel_->setAlignment(Qt::AlignCenter); - topLabel_->setFont(font); - - auto okBtn = new QPushButton(tr("Close"), this); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(15); - buttonLayout->addStretch(1); - buttonLayout->addWidget(okBtn); - - layout->addWidget(topLabel_); - layout->addWidget(userList_); - layout->addLayout(buttonLayout); - - auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); - connect(closeShortcut, &QShortcut::activated, this, &ReadReceipts::close); - connect(okBtn, &QPushButton::clicked, this, &ReadReceipts::close); -} - -void -ReadReceipts::addUsers(const std::multimap> &receipts) -{ - // We want to remove any previous items that have been set. - userList_->clear(); - - for (const auto &receipt : receipts) { - auto user = new ReceiptItem(this, - QString::fromStdString(receipt.second), - receipt.first, - ChatPage::instance()->currentRoom()); - auto item = new QListWidgetItem(userList_); - - item->setSizeHint(user->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - userList_->setItemWidget(item, user); - } -} - -void -ReadReceipts::paintEvent(QPaintEvent *) -{ - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - -void -ReadReceipts::hideEvent(QHideEvent *event) -{ - userList_->clear(); - QFrame::hideEvent(event); -} diff --git a/src/dialogs/ReadReceipts.h b/src/dialogs/ReadReceipts.h deleted file mode 100644 index 5c6c5d2b..00000000 --- a/src/dialogs/ReadReceipts.h +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include - -class Avatar; -class QLabel; -class QListWidget; -class QHBoxLayout; -class QVBoxLayout; - -namespace dialogs { - -class ReceiptItem : public QWidget -{ - Q_OBJECT - -public: - ReceiptItem(QWidget *parent, - const QString &user_id, - uint64_t timestamp, - const QString &room_id); - -protected: - void paintEvent(QPaintEvent *) override; - -private: - QString dateFormat(const QDateTime &then) const; - - QHBoxLayout *topLayout_; - QVBoxLayout *textLayout_; - - Avatar *avatar_; - - QLabel *userName_; - QLabel *timestamp_; -}; - -class ReadReceipts : public QFrame -{ - Q_OBJECT -public: - explicit ReadReceipts(QWidget *parent = nullptr); - -public slots: - void addUsers(const std::multimap> &users); - -protected: - void paintEvent(QPaintEvent *event) override; - void hideEvent(QHideEvent *event) override; - -private: - QLabel *topLabel_; - - QListWidget *userList_; -}; -} // dialogs diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index ee5564a5..f5737063 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -28,6 +28,7 @@ #include "MemberList.h" #include "MxcImageProvider.h" #include "Olm.h" +#include "ReadReceiptsModel.h" #include "TimelineViewManager.h" #include "Utils.h" #include "dialogs/RawMessage.h" @@ -1089,9 +1090,9 @@ TimelineModel::relatedInfo(QString id) } void -TimelineModel::readReceiptsAction(QString id) const +TimelineModel::showReadReceipts(QString id) { - MainWindow::instance()->openReadReceiptsDialog(id); + emit openReadReceiptsDialog(new ReadReceiptsModel{id, roomId(), this}); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0e2ce153..82fce257 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -20,6 +20,7 @@ #include "InviteesModel.h" #include "MemberList.h" #include "Permissions.h" +#include "ReadReceiptsModel.h" #include "ui/RoomSettings.h" #include "ui/UserProfile.h" @@ -241,7 +242,7 @@ public: Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); - Q_INVOKABLE void readReceiptsAction(QString id) const; + Q_INVOKABLE void showReadReceipts(QString id); Q_INVOKABLE void redactEvent(QString id); Q_INVOKABLE int idToIndex(QString id) const; Q_INVOKABLE QString indexToId(int index) const; @@ -348,6 +349,7 @@ signals: void typingUsersChanged(std::vector users); void replyChanged(QString reply); void editChanged(QString reply); + void openReadReceiptsDialog(ReadReceiptsModel *rr); void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); void scrollToIndex(int index); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index a6922be7..58b0d5a8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -26,6 +26,7 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "MxcImageProvider.h" +#include "ReadReceiptsModel.h" #include "RoomsModel.h" #include "SingleImagePackModel.h" #include "UserSettingsPage.h" @@ -205,6 +206,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "InviteesModel", "InviteesModel needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "ReadReceiptsModel", + "ReadReceiptsModel needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( -- cgit 1.5.1 From 774a9fdc3a5dd5221cd8143e2aaa03c7b93737f2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 21:58:57 -0400 Subject: Remove outdated comment --- src/ReadReceiptsModel.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 293733d3..eadb4e74 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -55,7 +55,6 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const case AvatarUrl: return readReceipts_[index.row()]->avatarUrl(); case Timestamp: - // the uint64_t to QVariant conversion was ambiguous, so... return readReceipts_[index.row()]->timestamp(); default: return {}; -- cgit 1.5.1 From 0d42909e406821b76c32b37af758a3721ea1238d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 23 Jul 2021 22:19:48 -0400 Subject: Simplify read receipt storage --- src/ReadReceiptsModel.cpp | 52 ++++++++++++++--------------------------------- src/ReadReceiptsModel.h | 41 ++++--------------------------------- 2 files changed, 19 insertions(+), 74 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index eadb4e74..8ee9cf45 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -26,12 +26,6 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject } } -ReadReceiptsModel::~ReadReceiptsModel() -{ - for (const auto &item : readReceipts_) - item->deleteLater(); -} - QHash ReadReceiptsModel::roleNames() const { @@ -49,13 +43,13 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const switch (role) { case Mxid: - return readReceipts_[index.row()]->mxid(); + return readReceipts_[index.row()].first; case DisplayName: - return readReceipts_[index.row()]->displayName(); + return cache::displayName(room_id_, readReceipts_[index.row()].first); case AvatarUrl: - return readReceipts_[index.row()]->avatarUrl(); + return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); case Timestamp: - return readReceipts_[index.row()]->timestamp(); + return dateFormat(readReceipts_[index.row()].second); default: return {}; } @@ -65,41 +59,25 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - std::multimap> unshown; + beginInsertRows(QModelIndex{}, readReceipts_.length(), users.size() - 1); + + readReceipts_.clear(); for (const auto &user : users) { - if (users_.find(user.first) == users_.end()) - unshown.emplace(user); + readReceipts_.push_back({QString::fromStdString(user.second), + QDateTime::fromMSecsSinceEpoch(user.first)}); } - beginInsertRows( - QModelIndex{}, readReceipts_.length(), readReceipts_.length() + unshown.size() - 1); - - for (const auto &user : unshown) - readReceipts_.push_back( - new ReadReceipt{QString::fromStdString(user.second), room_id_, user.first, this}); - - users_.merge(unshown); + std::sort(readReceipts_.begin(), + readReceipts_.end(), + [](const QPair &a, const QPair &b) { + return a.second > b.second; + }); endInsertRows(); } -ReadReceipt::ReadReceipt(QString mxid, QString room_id, uint64_t timestamp, QObject *parent) - : QObject{parent} - , mxid_{mxid} - , room_id_{room_id} - , displayName_{cache::displayName(room_id_, mxid_)} - , avatarUrl_{cache::avatarUrl(room_id_, mxid_)} - , timestamp_{timestamp} -{} - -QString -ReadReceipt::timestamp() const -{ - return dateFormat(QDateTime::fromMSecsSinceEpoch(timestamp_)); -} - QString -ReadReceipt::dateFormat(const QDateTime &then) const +ReadReceiptsModel::dateFormat(const QDateTime &then) const { auto now = QDateTime::currentDateTime(); auto days = then.daysTo(now); diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index d90bf7c1..98e41f8f 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -8,40 +8,7 @@ #include #include #include - -class ReadReceipt : public QObject -{ - Q_OBJECT - - Q_PROPERTY(QString mxid READ mxid CONSTANT) - Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) - Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged) - Q_PROPERTY(QString timestamp READ timestamp CONSTANT) - -public: - explicit ReadReceipt(QString mxid, - QString room_id, - uint64_t timestamp, - QObject *parent = nullptr); - - QString mxid() const { return mxid_; } - QString displayName() const { return displayName_; } - QString avatarUrl() const { return avatarUrl_; } - QString timestamp() const; - -signals: - void displayNameChanged(); - void avatarUrlChanged(); - -private: - QString dateFormat(const QDateTime &then) const; - - QString mxid_; - QString room_id_; - QString displayName_; - QString avatarUrl_; - uint64_t timestamp_; -}; +#include class ReadReceiptsModel : public QAbstractListModel { @@ -60,7 +27,6 @@ public: }; explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); - ~ReadReceiptsModel() override; QString eventId() const { return event_id_; } QString roomId() const { return room_id_; } @@ -77,10 +43,11 @@ public slots: void addUsers(const std::multimap> &users); private: + QString dateFormat(const QDateTime &then) const; + QString event_id_; QString room_id_; - QVector readReceipts_; - std::multimap> users_; + QVector> readReceipts_; }; #endif // READRECEIPTSMODEL_H -- cgit 1.5.1 From 8a329d65174d091a5c4d1542d0e74c7d576ee3c6 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:16:29 -0400 Subject: Remove Avatar class RIP --- CMakeLists.txt | 2 - src/MemberList.cpp | 1 - src/MemberList.h | 3 +- src/ui/Avatar.cpp | 168 ----------------------------------------------------- src/ui/Avatar.h | 48 --------------- 5 files changed, 2 insertions(+), 220 deletions(-) delete mode 100644 src/ui/Avatar.cpp delete mode 100644 src/ui/Avatar.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e9371579..8fc8e19d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -304,7 +304,6 @@ set(SRC_FILES src/timeline/RoomlistModel.cpp # UI components - src/ui/Avatar.cpp src/ui/Badge.cpp src/ui/DropShadow.cpp src/ui/FlatButton.cpp @@ -516,7 +515,6 @@ qt5_wrap_cpp(MOC_HEADERS src/timeline/RoomlistModel.h # UI components - src/ui/Avatar.h src/ui/Badge.h src/ui/FlatButton.h src/ui/FloatingButton.h diff --git a/src/MemberList.cpp b/src/MemberList.cpp index 0ef3b696..fb4f5ac2 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -20,7 +20,6 @@ #include "Logging.h" #include "Utils.h" #include "timeline/TimelineViewManager.h" -#include "ui/Avatar.h" MemberList::MemberList(const QString &room_id, QObject *parent) : QAbstractListModel{parent} diff --git a/src/MemberList.h b/src/MemberList.h index 9932f6a4..e6522694 100644 --- a/src/MemberList.h +++ b/src/MemberList.h @@ -4,9 +4,10 @@ #pragma once -#include "CacheStructs.h" #include +#include "CacheStructs.h" + class MemberList : public QAbstractListModel { Q_OBJECT diff --git a/src/ui/Avatar.cpp b/src/ui/Avatar.cpp deleted file mode 100644 index 154a0e2c..00000000 --- a/src/ui/Avatar.cpp +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include -#include -#include - -#include "AvatarProvider.h" -#include "Utils.h" -#include "ui/Avatar.h" - -Avatar::Avatar(QWidget *parent, int size) - : QWidget(parent) - , size_(size) -{ - type_ = ui::AvatarType::Letter; - letter_ = "A"; - - QFont _font(font()); - _font.setPointSizeF(ui::FontSize); - setFont(_font); - - QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); - setSizePolicy(policy); -} - -QColor -Avatar::textColor() const -{ - if (!text_color_.isValid()) - return QColor("black"); - - return text_color_; -} - -QColor -Avatar::backgroundColor() const -{ - if (!text_color_.isValid()) - return QColor("white"); - - return background_color_; -} - -QSize -Avatar::sizeHint() const -{ - return QSize(size_ + 2, size_ + 2); -} - -void -Avatar::setTextColor(const QColor &color) -{ - text_color_ = color; -} - -void -Avatar::setBackgroundColor(const QColor &color) -{ - background_color_ = color; -} - -void -Avatar::setLetter(const QString &letter) -{ - letter_ = letter; - type_ = ui::AvatarType::Letter; - update(); -} - -void -Avatar::setImage(const QString &avatar_url) -{ - avatar_url_ = avatar_url; - AvatarProvider::resolve(avatar_url, - static_cast(size_ * pixmap_.devicePixelRatio()), - this, - [this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) { - if (pm.isNull()) - return; - type_ = ui::AvatarType::Image; - pixmap_ = pm; - pixmap_.setDevicePixelRatio(requestedRatio); - update(); - }); -} - -void -Avatar::setImage(const QString &room, const QString &user) -{ - room_ = room; - user_ = user; - AvatarProvider::resolve(room, - user, - static_cast(size_ * pixmap_.devicePixelRatio()), - this, - [this, requestedRatio = pixmap_.devicePixelRatio()](QPixmap pm) { - if (pm.isNull()) - return; - type_ = ui::AvatarType::Image; - pixmap_ = pm; - pixmap_.setDevicePixelRatio(requestedRatio); - update(); - }); -} - -void -Avatar::setDevicePixelRatio(double ratio) -{ - if (type_ == ui::AvatarType::Image && abs(pixmap_.devicePixelRatio() - ratio) > 0.01) { - pixmap_ = pixmap_.scaled(QSize(size_, size_) * ratio); - pixmap_.setDevicePixelRatio(ratio); - - if (!avatar_url_.isEmpty()) - setImage(avatar_url_); - else - setImage(room_, user_); - } -} - -void -Avatar::paintEvent(QPaintEvent *) -{ - bool rounded = QSettings().value(QStringLiteral("user/avatar_circles"), true).toBool(); - - QPainter painter(this); - - painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | - QPainter::TextAntialiasing); - - QRectF r = rect(); - const int hs = size_ / 2; - - if (type_ != ui::AvatarType::Image) { - QBrush brush; - brush.setStyle(Qt::SolidPattern); - brush.setColor(backgroundColor()); - - painter.setPen(Qt::NoPen); - painter.setBrush(brush); - rounded ? painter.drawEllipse(r) : painter.drawRoundedRect(r, 3, 3); - } else if (painter.isActive()) { - setDevicePixelRatio(painter.device()->devicePixelRatioF()); - } - - switch (type_) { - case ui::AvatarType::Image: { - QPainterPath ppath; - - rounded ? ppath.addEllipse(width() / 2 - hs, height() / 2 - hs, size_, size_) - : ppath.addRoundedRect(r, 3, 3); - - painter.setClipPath(ppath); - painter.drawPixmap(QRect(width() / 2 - hs, height() / 2 - hs, size_, size_), - pixmap_); - break; - } - case ui::AvatarType::Letter: { - painter.setPen(textColor()); - painter.setBrush(Qt::NoBrush); - painter.drawText(r.translated(0, -1), Qt::AlignCenter, letter_); - break; - } - default: - break; - } -} diff --git a/src/ui/Avatar.h b/src/ui/Avatar.h deleted file mode 100644 index bbf05be3..00000000 --- a/src/ui/Avatar.h +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -#include "Theme.h" - -class Avatar : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) - Q_PROPERTY(QColor backgroundColor WRITE setBackgroundColor READ backgroundColor) - -public: - explicit Avatar(QWidget *parent = nullptr, int size = ui::AvatarSize); - - void setBackgroundColor(const QColor &color); - void setImage(const QString &avatar_url); - void setImage(const QString &room, const QString &user); - void setLetter(const QString &letter); - void setTextColor(const QColor &color); - void setDevicePixelRatio(double ratio); - - QColor backgroundColor() const; - QColor textColor() const; - - QSize sizeHint() const override; - -protected: - void paintEvent(QPaintEvent *event) override; - -private: - void init(); - - ui::AvatarType type_; - QString letter_; - QString avatar_url_, room_, user_; - QColor background_color_; - QColor text_color_; - QPixmap pixmap_; - int size_; -}; -- cgit 1.5.1 From 9c7bde22d10eef049c4fc267b3149db1b2f22343 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:16:58 -0400 Subject: Remove unused headers Why didn't I see these earlier? --- src/MemberList.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/MemberList.cpp b/src/MemberList.cpp index fb4f5ac2..196647fe 100644 --- a/src/MemberList.cpp +++ b/src/MemberList.cpp @@ -2,16 +2,6 @@ // // SPDX-License-Identifier: GPL-3.0-or-later -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "MemberList.h" #include "Cache.h" -- cgit 1.5.1 From 2be91b591dde67377cdd0c6125b8faf5a6b79cdd Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 09:17:06 -0400 Subject: make lint --- src/ReadReceiptsModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 98e41f8f..d7ff5fb8 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -6,9 +6,9 @@ #define READRECEIPTSMODEL_H #include +#include #include #include -#include class ReadReceiptsModel : public QAbstractListModel { -- cgit 1.5.1 From b03a1df19da3e7e6732cff7b21743d214336d00d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 12:51:45 -0400 Subject: Add close button at footer --- resources/qml/ReadReceipts.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 21b9b15e..b3bca9db 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -115,4 +115,10 @@ ApplicationWindow { } + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + onAccepted: readReceiptsRoot.close() + + } + } -- cgit 1.5.1 From 3ce7fdd63fda4fa31ee444448c829debe5e408f2 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 14:42:40 -0400 Subject: Fix incorrect function name --- resources/qml/MessageView.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 07feec8c..b6f2b909 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -580,7 +580,7 @@ ScrollView { Platform.MenuItem { text: qsTr("Read receip&ts") - onTriggered: room.readReceiptsAction(messageContextMenu.eventId) + onTriggered: room.showReadReceipts(messageContextMenu.eventId) } Platform.MenuItem { -- cgit 1.5.1 From 2fe010c04a90bb232f077a513a7ef6e31a97621a Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 15:35:28 -0400 Subject: Dynamically update read receipts --- resources/qml/ReadReceipts.qml | 1 - src/ReadReceiptsModel.cpp | 27 ++++++++++++++++++++++++--- src/ReadReceiptsModel.h | 1 + 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index b3bca9db..0756a2e7 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -118,7 +118,6 @@ ApplicationWindow { footer: DialogButtonBox { standardButtons: DialogButtonBox.Ok onAccepted: readReceiptsRoot.close() - } } diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 8ee9cf45..8a371922 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -7,6 +7,7 @@ #include #include "Cache.h" +#include "Cache_p.h" #include "Logging.h" #include "Utils.h" @@ -16,10 +17,26 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject , room_id_{room_id} { try { - addUsers(cache::readReceipts(event_id, room_id)); + addUsers(cache::readReceipts(event_id_, room_id_)); } catch (const lmdb::error &) { nhlog::db()->warn("failed to retrieve read receipts for {} {}", - event_id.toStdString(), + event_id_.toStdString(), + room_id_.toStdString()); + + return; + } + + connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update); +} + +void +ReadReceiptsModel::update() +{ + try { + addUsers(cache::readReceipts(event_id_, room_id_)); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve read receipts for {} {}", + event_id_.toStdString(), room_id_.toStdString()); return; @@ -59,7 +76,9 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - beginInsertRows(QModelIndex{}, readReceipts_.length(), users.size() - 1); + auto oldLen = readReceipts_.length(); + + beginInsertRows(QModelIndex{}, oldLen, users.size() - 1); readReceipts_.clear(); for (const auto &user : users) { @@ -74,6 +93,8 @@ ReadReceiptsModel::addUsers( }); endInsertRows(); + + emit dataChanged(index(0), index(oldLen - 1)); } QString diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index d7ff5fb8..f2e39f88 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -41,6 +41,7 @@ public: public slots: void addUsers(const std::multimap> &users); + void update(); private: QString dateFormat(const QDateTime &then) const; -- cgit 1.5.1 From 9dc9152e075804200158a1f6c6fb8f6e10961221 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 24 Jul 2021 18:38:22 -0400 Subject: Close dialog on escape --- resources/qml/ReadReceipts.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 0756a2e7..da2a5f66 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -21,6 +21,11 @@ ApplicationWindow { palette: Nheko.colors color: Nheko.colors.window + Shortcut { + sequence: StandardKey.Cancel + onActivated: readReceiptsRoot.close() + } + ColumnLayout { anchors.fill: parent anchors.margins: Nheko.paddingMedium -- cgit 1.5.1 From 5d38b96bbba01369e3b0238579bab64f74079fc5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 17:50:49 -0400 Subject: Use Dialog flag to make tiling WMs happy --- resources/qml/ReadReceipts.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index da2a5f66..84dc5666 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -20,6 +20,7 @@ ApplicationWindow { minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium palette: Nheko.colors color: Nheko.colors.window + flags: Qt.Dialog Shortcut { sequence: StandardKey.Cancel -- cgit 1.5.1 From 1777a1b52ffcb4e2d3fa0c394b14b3282ef6f3d5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 18:20:23 -0400 Subject: Reset model instead of doing weird convoluted updates --- src/ReadReceiptsModel.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 8a371922..936c6d61 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -76,9 +76,7 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - auto oldLen = readReceipts_.length(); - - beginInsertRows(QModelIndex{}, oldLen, users.size() - 1); + beginResetModel(); readReceipts_.clear(); for (const auto &user : users) { @@ -92,9 +90,7 @@ ReadReceiptsModel::addUsers( return a.second > b.second; }); - endInsertRows(); - - emit dataChanged(index(0), index(oldLen - 1)); + endResetModel(); } QString -- cgit 1.5.1 From 7e538851d6e3779434722e56a968e9f8b8a9da0d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Wed, 28 Jul 2021 21:31:37 -0400 Subject: Use a QSortFilterProxyModel instead of resetting the model --- CMakeLists.txt | 4 +-- resources/qml/ReadReceipts.qml | 4 +-- src/ReadReceiptsModel.cpp | 53 +++++++++++++++++++++++++----------- src/ReadReceiptsModel.h | 27 ++++++++++++++++-- src/timeline/TimelineModel.cpp | 2 +- src/timeline/TimelineModel.h | 2 +- src/timeline/TimelineViewManager.cpp | 6 ++-- 7 files changed, 70 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fc8e19d..80ea628f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,7 +350,7 @@ set(SRC_FILES src/MemberList.cpp src/MxcImageProvider.cpp src/Olm.cpp - src/ReadReceiptsModel.cpp + src/ReadReceiptsModel.cpp src/RegisterPage.cpp src/SSOHandler.cpp src/CombinedImagePackModel.cpp @@ -555,7 +555,7 @@ qt5_wrap_cpp(MOC_HEADERS src/MainWindow.h src/MemberList.h src/MxcImageProvider.h - src/ReadReceiptsModel.h + src/ReadReceiptsModel.h src/RegisterPage.h src/SSOHandler.h src/CombinedImagePackModel.h diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 84dc5666..5f213328 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -10,7 +10,7 @@ import im.nheko 1.0 ApplicationWindow { id: readReceiptsRoot - property ReadReceiptsModel readReceipts + property ReadReceiptsProxy readReceipts x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) @@ -86,7 +86,7 @@ ApplicationWindow { ToolTip.text: model.mxid TapHandler { - onSingleTapped: chat.model.openUserProfile(userId) + onSingleTapped: Rooms.currentRoom.openUserProfile(userId) } CursorShape { diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 936c6d61..0be22be2 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -46,10 +46,13 @@ ReadReceiptsModel::update() QHash ReadReceiptsModel::roleNames() const { - return {{Mxid, "mxid"}, - {DisplayName, "displayName"}, - {AvatarUrl, "avatarUrl"}, - {Timestamp, "timestamp"}}; + // Note: RawTimestamp is purposely not included here + return { + {Mxid, "mxid"}, + {DisplayName, "displayName"}, + {AvatarUrl, "avatarUrl"}, + {Timestamp, "timestamp"}, + }; } QVariant @@ -67,6 +70,8 @@ ReadReceiptsModel::data(const QModelIndex &index, int role) const return cache::avatarUrl(room_id_, readReceipts_[index.row()].first); case Timestamp: return dateFormat(readReceipts_[index.row()].second); + case RawTimestamp: + return readReceipts_[index.row()].second; default: return {}; } @@ -76,21 +81,22 @@ void ReadReceiptsModel::addUsers( const std::multimap> &users) { - beginResetModel(); + auto newReceipts = users.size() - readReceipts_.size(); - readReceipts_.clear(); - for (const auto &user : users) { - readReceipts_.push_back({QString::fromStdString(user.second), - QDateTime::fromMSecsSinceEpoch(user.first)}); - } + if (newReceipts > 0) { + beginInsertRows( + QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1); - std::sort(readReceipts_.begin(), - readReceipts_.end(), - [](const QPair &a, const QPair &b) { - return a.second > b.second; - }); + for (const auto &user : users) { + QPair item = { + QString::fromStdString(user.second), + QDateTime::fromMSecsSinceEpoch(user.first)}; + if (!readReceipts_.contains(item)) + readReceipts_.push_back(item); + } - endResetModel(); + endInsertRows(); + } } QString @@ -112,3 +118,18 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const return QLocale::system().toString(then.time(), QLocale::ShortFormat); } + +ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent) + : QSortFilterProxyModel{parent} + , model_{event_id, room_id, this} +{ + setSourceModel(&model_); + setSortRole(ReadReceiptsModel::RawTimestamp); +} + +bool +ReadReceiptsProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const +{ + // since we are sorting from greatest to least timestamp, return something that looks totally backwards! + return source_left.data().toULongLong() > source_right.data().toULongLong(); +} diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index f2e39f88..9e26bcd5 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -8,15 +8,13 @@ #include #include #include +#include #include class ReadReceiptsModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString eventId READ eventId CONSTANT) - Q_PROPERTY(QString roomId READ roomId CONSTANT) - public: enum Roles { @@ -24,6 +22,7 @@ public: DisplayName, AvatarUrl, Timestamp, + RawTimestamp, }; explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr); @@ -51,4 +50,26 @@ private: QVector> readReceipts_; }; +class ReadReceiptsProxy : public QSortFilterProxyModel +{ + Q_OBJECT + + Q_PROPERTY(QString eventId READ eventId CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + +public: + explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr); + + QString eventId() const { return event_id_; } + QString roomId() const { return room_id_; } + + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; + +private: + QString event_id_; + QString room_id_; + + ReadReceiptsModel model_; +}; + #endif // READRECEIPTSMODEL_H diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f5737063..6ae0c4d1 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -1092,7 +1092,7 @@ TimelineModel::relatedInfo(QString id) void TimelineModel::showReadReceipts(QString id) { - emit openReadReceiptsDialog(new ReadReceiptsModel{id, roomId(), this}); + emit openReadReceiptsDialog(new ReadReceiptsProxy{id, roomId(), this}); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 82fce257..0d5f7109 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -349,7 +349,7 @@ signals: void typingUsersChanged(std::vector users); void replyChanged(QString reply); void editChanged(QString reply); - void openReadReceiptsDialog(ReadReceiptsModel *rr); + void openReadReceiptsDialog(ReadReceiptsProxy *rr); void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); void scrollToIndex(int index); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 58b0d5a8..76bc127e 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -206,12 +206,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "InviteesModel", "InviteesModel needs to be instantiated on the C++ side"); - qmlRegisterUncreatableType( + qmlRegisterUncreatableType( "im.nheko", 1, 0, - "ReadReceiptsModel", - "ReadReceiptsModel needs to be instantiated on the C++ side"); + "ReadReceiptsProxy", + "ReadReceiptsProxy needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( -- cgit 1.5.1 From 368e13fac38e22b5c0ce4669de63dcdadbb31116 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 29 Jul 2021 20:49:37 -0400 Subject: Use built-in sorting so that dynamic updates work --- src/ReadReceiptsModel.cpp | 9 ++------- src/ReadReceiptsModel.h | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 0be22be2..d8b7141f 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -125,11 +125,6 @@ ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject { setSourceModel(&model_); setSortRole(ReadReceiptsModel::RawTimestamp); -} - -bool -ReadReceiptsProxy::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const -{ - // since we are sorting from greatest to least timestamp, return something that looks totally backwards! - return source_left.data().toULongLong() > source_right.data().toULongLong(); + sort(0, Qt::DescendingOrder); + setDynamicSortFilter(true); } diff --git a/src/ReadReceiptsModel.h b/src/ReadReceiptsModel.h index 9e26bcd5..3b45716c 100644 --- a/src/ReadReceiptsModel.h +++ b/src/ReadReceiptsModel.h @@ -63,8 +63,6 @@ public: QString eventId() const { return event_id_; } QString roomId() const { return room_id_; } - bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; - private: QString event_id_; QString room_id_; -- cgit 1.5.1 From 135622e14e8ff3bba32becce722d986e0abf11f5 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Thu, 29 Jul 2021 21:29:09 -0400 Subject: Don't switch room that read receipt-related stuff is opened in --- resources/qml/ReadReceipts.qml | 5 +++-- resources/qml/Root.qml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 5f213328..db5d2e36 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -11,6 +11,7 @@ ApplicationWindow { id: readReceiptsRoot property ReadReceiptsProxy readReceipts + property Room room x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) @@ -65,7 +66,7 @@ ApplicationWindow { userid: model.mxid url: model.avatarUrl.replace("mxc://", "image://MxcImage/") displayName: model.displayName - onClicked: Rooms.currentRoom.openUserProfile(model.mxid) + onClicked: room.openUserProfile(model.mxid) ToolTip.visible: avatarHover.hovered ToolTip.text: model.mxid @@ -86,7 +87,7 @@ ApplicationWindow { ToolTip.text: model.mxid TapHandler { - onSingleTapped: Rooms.currentRoom.openUserProfile(userId) + onSingleTapped: room.openUserProfile(userId) } CursorShape { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index a099b5e6..a7684af5 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -173,9 +173,10 @@ Page { } Connections { - function onOpenReadReceiptsDialog() { + function onOpenReadReceiptsDialog(rr) { var dialog = readReceiptsDialog.createObject(timelineRoot, { - "readReceipts": rr + "readReceipts": rr, + "room": Rooms.currentRoom }); dialog.show(); } -- cgit 1.5.1 From 6409462a9643531218b4085385806779f7a22fd8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 30 Jul 2021 03:31:29 +0200 Subject: Rate limit olm session creation --- src/Olm.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index d20bf9a4..d421e336 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1138,9 +1138,23 @@ send_encrypted_to_device_messages(const std::map, qint64> rateLimit; + auto currentTime = QDateTime::currentSecsSinceEpoch(); + if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 < + currentTime) { + claims.one_time_keys[user][device] = + mtx::crypto::SIGNED_CURVE25519; + pks[user][device].ed25519 = d.keys.at("ed25519:" + device); + pks[user][device].curve25519 = + d.keys.at("curve25519:" + device); + + rateLimit.insert(QPair(user, device), currentTime); + } else { + nhlog::crypto()->warn("Not creating new session with {}:{} " + "because of rate limit", + user, + device); + } continue; } -- cgit 1.5.1 From e4cd8b1c11864ab4d23c241ed3c519e2b1f067e5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 30 Jul 2021 03:31:49 +0200 Subject: Log how many rooms we loaded --- src/timeline/RoomlistModel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index f7f377fb..f4c927ac 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -533,6 +533,8 @@ RoomlistModel::initializeRooms() for (const auto &id : cache::client()->roomIds()) addRoom(id, true); + nhlog::db()->info("Restored {} rooms from cache", rowCount()); + endResetModel(); } -- cgit 1.5.1 From e7877ae5af533f75ee8fade52f968c453df7c201 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 30 Jul 2021 12:44:08 +0200 Subject: Fix crash when we don't have keys for other device when receiving an olm message from it --- src/Olm.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Olm.cpp b/src/Olm.cpp index d421e336..e3ca1c34 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -286,11 +286,17 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey bool from_their_device = false; for (auto [device_id, key] : otherUserDeviceKeys.device_keys) { - if (key.keys.at("curve25519:" + device_id) == msg.sender_key) { - if (key.keys.at("ed25519:" + device_id) == sender_ed25519) { - from_their_device = true; - break; - } + auto c_key = key.keys.find("curve25519:" + device_id); + auto e_key = key.keys.find("ed25519:" + device_id); + + if (c_key == key.keys.end() || e_key == key.keys.end()) { + nhlog::crypto()->warn( + "Skipping device {} as we have no keys for it.", + device_id); + } else if (c_key->second == msg.sender_key && + e_key->second == sender_ed25519) { + from_their_device = true; + break; } } if (!from_their_device) { -- cgit 1.5.1 From 330b9d62a580fbd4c79925f511ab4c2e2200ad60 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 07:24:48 -0400 Subject: Move read receipts connection to allow for future pop-out room views --- resources/qml/Root.qml | 12 ------------ resources/qml/TimelineView.qml | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index a7684af5..7d91beae 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -172,18 +172,6 @@ Page { target: TimelineManager } - Connections { - function onOpenReadReceiptsDialog(rr) { - var dialog = readReceiptsDialog.createObject(timelineRoot, { - "readReceipts": rr, - "room": Rooms.currentRoom - }); - dialog.show(); - } - - target: Rooms.currentRoom - } - Connections { function onNewInviteState() { if (CallManager.haveCallInvite && Settings.mobileMode) { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c5cc69a6..d19f2cc9 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -249,4 +249,16 @@ Item { roomid: room ? room.roomId : "" } + Connections { + function onOpenReadReceiptsDialog(rr) { + var dialog = readReceiptsDialog.createObject(timelineRoot, { + "readReceipts": rr, + "room": room + }); + dialog.show(); + } + + target: room + } + } -- cgit 1.5.1 From 3cb4209d7b3c6a0b8455f49b991b897adf302572 Mon Sep 17 00:00:00 2001 From: Loren Burkholder <55629213+LorenDB@users.noreply.github.com> Date: Fri, 30 Jul 2021 07:56:25 -0400 Subject: Reformat dates Co-authored-by: DeepBlueV7.X --- src/ReadReceiptsModel.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index d8b7141f..562353a7 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -106,13 +106,14 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return tr("Today %1") + return tr("Today, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 2) - return tr("Yesterday %1") + return tr("Yesterday, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 7) - return QString("%1 %2") + //: %1 is the name of the current day, %2 is the time the read receipt was read. The result may look like this: Monday, 7:15 + return QString("%1, %2") .arg(then.toString("dddd")) .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); -- cgit 1.5.1 From b398454409e3197ebf1b7d501e106bbb46523073 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:14:44 -0400 Subject: Use an explicit color for the label --- resources/qml/ReadReceipts.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index db5d2e36..8869d813 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -36,6 +36,7 @@ ApplicationWindow { Label { id: headerTitle + color: Nheko.colors.text Layout.alignment: Qt.AlignCenter text: qsTr("Read receipts") font.pointSize: fontMetrics.font.pointSize * 1.5 -- cgit 1.5.1 From 7dcdd51a8b329178152526dee875ba4980e4993d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:19:05 -0400 Subject: make lint --- src/ReadReceiptsModel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 562353a7..059f5d53 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -112,7 +112,8 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const return tr("Yesterday, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); else if (days < 7) - //: %1 is the name of the current day, %2 is the time the read receipt was read. The result may look like this: Monday, 7:15 + //: %1 is the name of the current day, %2 is the time the read receipt was read. The + //: result may look like this: Monday, 7:15 return QString("%1, %2") .arg(then.toString("dddd")) .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); -- cgit 1.5.1 From f48f244dcbfa319c5b8092791231fe56ac70bb8d Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 08:44:07 -0400 Subject: Use correct date format --- src/ReadReceiptsModel.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ReadReceiptsModel.cpp b/src/ReadReceiptsModel.cpp index 059f5d53..25262c59 100644 --- a/src/ReadReceiptsModel.cpp +++ b/src/ReadReceiptsModel.cpp @@ -106,8 +106,7 @@ ReadReceiptsModel::dateFormat(const QDateTime &then) const auto days = then.daysTo(now); if (days == 0) - return tr("Today, %1") - .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); + return QLocale::system().toString(then.time(), QLocale::ShortFormat); else if (days < 2) return tr("Yesterday, %1") .arg(QLocale::system().toString(then.time(), QLocale::ShortFormat)); -- cgit 1.5.1 From 5b0bd26795abdf222d0cfd3e5ee3cf8e8b41a9c9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 31 Jul 2021 11:04:47 +0200 Subject: Fix annoying touch overlap in room list --- resources/qml/MessageView.qml | 4 ++-- resources/qml/RoomList.qml | 43 +++++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index b6f2b909..9ba5e2d0 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -212,9 +212,9 @@ ScrollView { // force current read index to update onTriggered: { - if (chat.model) { + if (chat.model) chat.model.setCurrentIndex(chat.model.currentIndex); - } + } interval: 1000 } diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index a2e50fab..695b08f3 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -172,31 +172,38 @@ Page { } ] - TapHandler { - margin: -Nheko.paddingSmall - acceptedButtons: Qt.RightButton - onSingleTapped: { - if (!TimelineManager.isInvite) - roomContextMenu.show(roomId, tags); + // NOTE(Nico): We want to prevent the touch areas from overlapping. For some reason we need to add 1px of padding for that... + Item { + anchors.fill: parent + anchors.margins: 1 + TapHandler { + acceptedButtons: Qt.RightButton + onSingleTapped: { + if (!TimelineManager.isInvite) + roomContextMenu.show(roomId, tags); + + } + gesturePolicy: TapHandler.ReleaseWithinBounds + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | DeviceType.TouchPad } - gesturePolicy: TapHandler.ReleaseWithinBounds - } - TapHandler { - margin: -Nheko.paddingSmall - onSingleTapped: Rooms.setCurrentRoom(roomId) - onLongPressed: { - if (!isInvite) - roomContextMenu.show(roomId, tags); + TapHandler { + margin: -Nheko.paddingSmall + onSingleTapped: Rooms.setCurrentRoom(roomId) + onLongPressed: { + if (!isInvite) + roomContextMenu.show(roomId, tags); + } } - } - HoverHandler { - id: hovered + HoverHandler { + id: hovered + + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | DeviceType.TouchPad + } - margin: -Nheko.paddingSmall } RowLayout { -- cgit 1.5.1 From 4c151cc3c7a6722930ea2b957d63204dd62b15ed Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 31 Jul 2021 15:59:19 +0200 Subject: Fix C&P error for DeviceType --- resources/qml/RoomList.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index 695b08f3..cbc65fc0 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -185,7 +185,7 @@ Page { } gesturePolicy: TapHandler.ReleaseWithinBounds - acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | DeviceType.TouchPad + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } TapHandler { @@ -201,7 +201,7 @@ Page { HoverHandler { id: hovered - acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | DeviceType.TouchPad + acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad } } -- cgit 1.5.1 From 760f6757923c9aba2ecd7d89fc0ddfd43a741c6d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 31 Jul 2021 17:59:03 +0200 Subject: Ensure the encrypted rooms db is always created --- src/Cache.cpp | 9 +++++---- src/Cache_p.h | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 4c24a712..7d0b1a89 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -288,6 +288,9 @@ Cache::setup() outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE); megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE); + // What rooms are encrypted + encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + txn.commit(); databaseReady_ = true; @@ -298,8 +301,7 @@ Cache::setEncryptedRoom(lmdb::txn &txn, const std::string &room_id) { nhlog::db()->info("mark room {} as encrypted", room_id); - auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - db.put(txn, room_id, "0"); + encryptedRooms_.put(txn, room_id, "0"); } bool @@ -308,8 +310,7 @@ Cache::isRoomEncrypted(const std::string &room_id) std::string_view unused; auto txn = ro_txn(env_); - auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); - auto res = db.get(txn, room_id, unused); + auto res = encryptedRooms_.get(txn, room_id, unused); return res; } diff --git a/src/Cache_p.h b/src/Cache_p.h index 89c88925..18b9601f 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -689,6 +689,8 @@ private: lmdb::dbi outboundMegolmSessionDb_; lmdb::dbi megolmSessionDataDb_; + lmdb::dbi encryptedRooms_; + QString localUserId_; QString cacheDirectory_; -- cgit 1.5.1 From dab1c9068ac6d48a1faba54d7510deb360ae74e3 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Fri, 30 Jul 2021 22:13:58 -0400 Subject: QML the raw message dialog --- CMakeLists.txt | 1 - resources/qml/RawMessageDialog.qml | 46 +++++++++++++++++++++++++++++ resources/qml/Root.qml | 8 +++++ resources/qml/TimelineView.qml | 7 +++++ resources/res.qrc | 1 + src/dialogs/RawMessage.h | 60 -------------------------------------- src/timeline/TimelineModel.cpp | 11 +++---- src/timeline/TimelineModel.h | 5 ++-- src/ui/NhekoGlobalObject.h | 5 ++++ 9 files changed, 74 insertions(+), 70 deletions(-) create mode 100644 resources/qml/RawMessageDialog.qml delete mode 100644 src/dialogs/RawMessage.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 80ea628f..9f824048 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,7 +496,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/LeaveRoom.h src/dialogs/Logout.h src/dialogs/PreviewUploadOverlay.h - src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h # Emoji diff --git a/resources/qml/RawMessageDialog.qml b/resources/qml/RawMessageDialog.qml new file mode 100644 index 00000000..62a5770f --- /dev/null +++ b/resources/qml/RawMessageDialog.qml @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import im.nheko 1.0 + +ApplicationWindow { + id: rawMessageRoot + + property alias rawMessage: rawMessageView.text + + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 420 + width: 420 + palette: Nheko.colors + color: Nheko.colors.window + flags: Qt.Tool | Qt.WindowStaysOnTopHint + + Shortcut { + sequence: StandardKey.Cancel + onActivated: rawMessageRoot.close() + } + + ScrollView { + anchors.fill: parent + palette: Nheko.colors + padding: Nheko.paddingMedium + + TextArea { + id: rawMessageView + + font: Nheko.monospaceFont() + palette: Nheko.colors + readOnly: true + } + + } + + footer: DialogButtonBox { + standardButtons: DialogButtonBox.Ok + onAccepted: rawMessageRoot.close() + } +} diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 7d91beae..70cfbda5 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -104,6 +104,14 @@ Page { } + Component { + id: rawMessageDialog + + RawMessageDialog { + } + + } + Shortcut { sequence: "Ctrl+K" onActivated: { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index d19f2cc9..e4036eb7 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -258,6 +258,13 @@ Item { dialog.show(); } + function onShowRawMessageDialog(rawMessage) { + var dialog = rawMessageDialog.createObject(timelineRoot, { + "rawMessage": rawMessage + }); + dialog.show(); + } + target: room } diff --git a/resources/res.qrc b/resources/res.qrc index 2b655b9e..c911653c 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -177,6 +177,7 @@ qml/RoomMembers.qml qml/InviteDialog.qml qml/ReadReceipts.qml + qml/RawMessageDialog.qml media/ring.ogg diff --git a/src/dialogs/RawMessage.h b/src/dialogs/RawMessage.h deleted file mode 100644 index e95f675c..00000000 --- a/src/dialogs/RawMessage.h +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include -#include -#include - -#include "nlohmann/json.hpp" - -#include "Logging.h" -#include "MainWindow.h" -#include "ui/FlatButton.h" - -namespace dialogs { - -class RawMessage : public QWidget -{ - Q_OBJECT -public: - RawMessage(QString msg, QWidget *parent = nullptr) - : QWidget{parent} - { - QFont monospaceFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - - auto layout = new QVBoxLayout{this}; - auto viewer = new QTextBrowser{this}; - viewer->setFont(monospaceFont); - viewer->setText(msg); - - layout->setSpacing(0); - layout->setMargin(0); - layout->addWidget(viewer); - - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setAttribute(Qt::WA_DeleteOnClose, true); - - QSize winsize; - QPoint center; - - auto window = MainWindow::instance(); - if (window) { - winsize = window->frameGeometry().size(); - center = window->frameGeometry().center(); - - move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); - } else { - nhlog::ui()->warn("unable to retrieve MainWindow's size"); - } - - raise(); - show(); - } -}; -} // namespace dialogs diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 6ae0c4d1..a8adf05b 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -31,7 +31,6 @@ #include "ReadReceiptsModel.h" #include "TimelineViewManager.h" #include "Utils.h" -#include "dialogs/RawMessage.h" Q_DECLARE_METATYPE(QModelIndex) @@ -1026,14 +1025,13 @@ TimelineModel::formatDateSeparator(QDate date) const } void -TimelineModel::viewRawMessage(QString id) const +TimelineModel::viewRawMessage(QString id) { auto e = events.get(id.toStdString(), "", false); if (!e) return; std::string ev = mtx::accessors::serialize_event(*e).dump(4); - auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); - Q_UNUSED(dialog); + emit showRawMessageDialog(QString::fromStdString(ev)); } void @@ -1047,15 +1045,14 @@ TimelineModel::forwardMessage(QString eventId, QString roomId) } void -TimelineModel::viewDecryptedRawMessage(QString id) const +TimelineModel::viewDecryptedRawMessage(QString id) { auto e = events.get(id.toStdString(), ""); if (!e) return; std::string ev = mtx::accessors::serialize_event(*e).dump(4); - auto dialog = new dialogs::RawMessage(QString::fromStdString(ev)); - Q_UNUSED(dialog); + emit showRawMessageDialog(QString::fromStdString(ev)); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 0d5f7109..f62c5360 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -236,9 +236,9 @@ public: Q_INVOKABLE QString formatGuestAccessEvent(QString id); Q_INVOKABLE QString formatPowerLevelEvent(QString id); - Q_INVOKABLE void viewRawMessage(QString id) const; + Q_INVOKABLE void viewRawMessage(QString id); Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); - Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; + Q_INVOKABLE void viewDecryptedRawMessage(QString id); Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void replyAction(QString id); @@ -350,6 +350,7 @@ signals: void replyChanged(QString reply); void editChanged(QString reply); void openReadReceiptsDialog(ReadReceiptsProxy *rr); + void showRawMessageDialog(QString rawMessage); void paginationInProgressChanged(const bool); void newCallEvent(const mtx::events::collections::TimelineEvents &event); void scrollToIndex(int index); diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index 14135fd1..cfe982c5 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -38,6 +39,10 @@ public: int paddingLarge() const { return 20; } UserProfile *currentUser() const; + Q_INVOKABLE QFont monospaceFont() const + { + return QFontDatabase::systemFont(QFontDatabase::FixedFont); + } Q_INVOKABLE void openLink(QString link) const; Q_INVOKABLE void setStatusMessage(QString msg) const; Q_INVOKABLE void showUserSettingsPage() const; -- cgit 1.5.1 From 092f936fc9efed519b83288eeda386daa9af6a91 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Sat, 31 Jul 2021 13:55:56 -0400 Subject: Fix colors for manual dark theme --- resources/qml/RawMessageDialog.qml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/qml/RawMessageDialog.qml b/resources/qml/RawMessageDialog.qml index 62a5770f..231e2f6d 100644 --- a/resources/qml/RawMessageDialog.qml +++ b/resources/qml/RawMessageDialog.qml @@ -25,6 +25,7 @@ ApplicationWindow { } ScrollView { + anchors.margins: Nheko.paddingMedium anchors.fill: parent palette: Nheko.colors padding: Nheko.paddingMedium @@ -33,8 +34,12 @@ ApplicationWindow { id: rawMessageView font: Nheko.monospaceFont() - palette: Nheko.colors + color: Nheko.colors.text readOnly: true + + background: Rectangle { + color: Nheko.colors.base + } } } -- cgit 1.5.1 From 25e7a985b82bbeff9c3c5b4d15b44f2539768260 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 1 Aug 2021 00:59:46 +0200 Subject: Add option to only send encrypted messages to verified devices fixes #636 --- src/Cache.cpp | 49 ++++++++++++++++++-- src/Cache_p.h | 3 +- src/Olm.cpp | 3 +- src/UserSettingsPage.cpp | 113 +++++++++++++++++++++++++++++------------------ src/UserSettingsPage.h | 7 +++ 5 files changed, 127 insertions(+), 48 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 7d0b1a89..291df053 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3542,7 +3542,7 @@ Cache::roomMembers(const std::string &room_id) } std::map> -Cache::getMembersWithKeys(const std::string &room_id) +Cache::getMembersWithKeys(const std::string &room_id, bool verified_only) { std::string_view keys; @@ -3559,10 +3559,51 @@ Cache::getMembersWithKeys(const std::string &room_id) auto res = keysDb.get(txn, user_id, keys); if (res) { - members[std::string(user_id)] = - json::parse(keys).get(); + auto k = json::parse(keys).get(); + if (verified_only) { + auto verif = verificationStatus(std::string(user_id)); + if (verif.user_verified == crypto::Trust::Verified || + !verif.verified_devices.empty()) { + auto keyCopy = k; + keyCopy.device_keys.clear(); + + std::copy_if( + k.device_keys.begin(), + k.device_keys.end(), + std::inserter(keyCopy.device_keys, + keyCopy.device_keys.end()), + [&verif](const auto &key) { + auto curve25519 = key.second.keys.find( + "curve25519:" + key.first); + if (curve25519 == key.second.keys.end()) + return false; + if (auto t = + verif.verified_device_keys.find( + curve25519->second); + t == + verif.verified_device_keys.end() || + t->second != crypto::Trust::Verified) + return false; + + return key.first == + key.second.device_id && + std::find( + verif.verified_devices.begin(), + verif.verified_devices.end(), + key.first) != + verif.verified_devices.end(); + }); + + if (!keyCopy.device_keys.empty()) + members[std::string(user_id)] = + std::move(keyCopy); + } + } else { + members[std::string(user_id)] = std::move(k); + } } else { - members[std::string(user_id)] = {}; + if (!verified_only) + members[std::string(user_id)] = {}; } } cursor.close(); diff --git a/src/Cache_p.h b/src/Cache_p.h index 18b9601f..5d700658 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -48,7 +48,8 @@ public: // user cache stores user keys std::optional userKeys(const std::string &user_id); std::map> getMembersWithKeys( - const std::string &room_id); + const std::string &room_id, + bool verified_only); void updateUserKeys(const std::string &sync_token, const mtx::responses::QueryKeys &keyQuery); void markUserKeysOutOfDate(lmdb::txn &txn, diff --git a/src/Olm.cpp b/src/Olm.cpp index e3ca1c34..048a6c0f 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -524,7 +524,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, auto own_user_id = http::client()->user_id().to_string(); - auto members = cache::client()->getMembersWithKeys(room_id); + auto members = cache::client()->getMembersWithKeys( + room_id, UserSettings::instance()->onlyShareKeysWithVerifiedUsers()); std::map> sendSessionTo; mtx::crypto::OutboundGroupSessionPtr session = nullptr; diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index a062780a..ab6ac492 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -90,13 +90,11 @@ UserSettings::load(std::optional profile) decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool(); privacyScreen_ = settings.value("user/privacy_screen", false).toBool(); privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt(); - shareKeysWithTrustedUsers_ = - settings.value("user/automatically_share_keys_with_trusted_users", false).toBool(); - mobileMode_ = settings.value("user/mobile_mode", false).toBool(); - emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); - baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); - auto tempPresence = settings.value("user/presence", "").toString().toStdString(); - auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); + mobileMode_ = settings.value("user/mobile_mode", false).toBool(); + emojiFont_ = settings.value("user/emoji_font_family", "default").toString(); + baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble(); + auto tempPresence = settings.value("user/presence", "").toString().toStdString(); + auto presenceValue = QMetaEnum::fromType().keyToValue(tempPresence.c_str()); if (presenceValue < 0) presenceValue = 0; presence_ = static_cast(presenceValue); @@ -123,6 +121,12 @@ UserSettings::load(std::optional profile) userId_ = settings.value(prefix + "auth/user_id", "").toString(); deviceId_ = settings.value(prefix + "auth/device_id", "").toString(); + shareKeysWithTrustedUsers_ = + settings.value(prefix + "user/automatically_share_keys_with_trusted_users", false) + .toBool(); + onlyShareKeysWithVerifiedUsers_ = + settings.value(prefix + "user/only_share_keys_with_verified_users", false).toBool(); + disableCertificateValidation_ = settings.value("disable_certificate_validation", false).toBool(); @@ -401,6 +405,17 @@ UserSettings::setUseStunServer(bool useStunServer) save(); } +void +UserSettings::setOnlyShareKeysWithVerifiedUsers(bool shareKeys) +{ + if (shareKeys == onlyShareKeysWithVerifiedUsers_) + return; + + onlyShareKeysWithVerifiedUsers_ = shareKeys; + emit onlyShareKeysWithVerifiedUsersChanged(shareKeys); + save(); +} + void UserSettings::setShareKeysWithTrustedUsers(bool shareKeys) { @@ -610,8 +625,6 @@ UserSettings::save() settings.setValue("decrypt_sidebar", decryptSidebar_); settings.setValue("privacy_screen", privacyScreen_); settings.setValue("privacy_screen_timeout", privacyScreenTimeout_); - settings.setValue("automatically_share_keys_with_trusted_users", - shareKeysWithTrustedUsers_); settings.setValue("mobile_mode", mobileMode_); settings.setValue("font_size", baseFontSize_); settings.setValue("typing_notifications", typingNotifications_); @@ -650,6 +663,11 @@ UserSettings::save() settings.setValue(prefix + "auth/user_id", userId_); settings.setValue(prefix + "auth/device_id", deviceId_); + settings.setValue(prefix + "user/automatically_share_keys_with_trusted_users", + shareKeysWithTrustedUsers_); + settings.setValue(prefix + "user/only_share_keys_with_verified_users", + onlyShareKeysWithVerifiedUsers_); + settings.setValue("disable_certificate_validation", disableCertificateValidation_); settings.sync(); @@ -703,41 +721,43 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge general_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); general_->setFont(font); - trayToggle_ = new Toggle{this}; - startInTrayToggle_ = new Toggle{this}; - avatarCircles_ = new Toggle{this}; - decryptSidebar_ = new Toggle(this); - privacyScreen_ = new Toggle{this}; - shareKeysWithTrustedUsers_ = new Toggle(this); - groupViewToggle_ = new Toggle{this}; - timelineButtonsToggle_ = new Toggle{this}; - typingNotifications_ = new Toggle{this}; - messageHoverHighlight_ = new Toggle{this}; - enlargeEmojiOnlyMessages_ = new Toggle{this}; - sortByImportance_ = new Toggle{this}; - readReceipts_ = new Toggle{this}; - markdown_ = new Toggle{this}; - desktopNotifications_ = new Toggle{this}; - alertOnNotification_ = new Toggle{this}; - useStunServer_ = new Toggle{this}; - mobileMode_ = new Toggle{this}; - scaleFactorCombo_ = new QComboBox{this}; - fontSizeCombo_ = new QComboBox{this}; - fontSelectionCombo_ = new QFontComboBox{this}; - emojiFontSelectionCombo_ = new QComboBox{this}; - ringtoneCombo_ = new QComboBox{this}; - microphoneCombo_ = new QComboBox{this}; - cameraCombo_ = new QComboBox{this}; - cameraResolutionCombo_ = new QComboBox{this}; - cameraFrameRateCombo_ = new QComboBox{this}; - timelineMaxWidthSpin_ = new QSpinBox{this}; - privacyScreenTimeout_ = new QSpinBox{this}; + trayToggle_ = new Toggle{this}; + startInTrayToggle_ = new Toggle{this}; + avatarCircles_ = new Toggle{this}; + decryptSidebar_ = new Toggle(this); + privacyScreen_ = new Toggle{this}; + onlyShareKeysWithVerifiedUsers_ = new Toggle(this); + shareKeysWithTrustedUsers_ = new Toggle(this); + groupViewToggle_ = new Toggle{this}; + timelineButtonsToggle_ = new Toggle{this}; + typingNotifications_ = new Toggle{this}; + messageHoverHighlight_ = new Toggle{this}; + enlargeEmojiOnlyMessages_ = new Toggle{this}; + sortByImportance_ = new Toggle{this}; + readReceipts_ = new Toggle{this}; + markdown_ = new Toggle{this}; + desktopNotifications_ = new Toggle{this}; + alertOnNotification_ = new Toggle{this}; + useStunServer_ = new Toggle{this}; + mobileMode_ = new Toggle{this}; + scaleFactorCombo_ = new QComboBox{this}; + fontSizeCombo_ = new QComboBox{this}; + fontSelectionCombo_ = new QFontComboBox{this}; + emojiFontSelectionCombo_ = new QComboBox{this}; + ringtoneCombo_ = new QComboBox{this}; + microphoneCombo_ = new QComboBox{this}; + cameraCombo_ = new QComboBox{this}; + cameraResolutionCombo_ = new QComboBox{this}; + cameraFrameRateCombo_ = new QComboBox{this}; + timelineMaxWidthSpin_ = new QSpinBox{this}; + privacyScreenTimeout_ = new QSpinBox{this}; trayToggle_->setChecked(settings_->tray()); startInTrayToggle_->setChecked(settings_->startInTray()); avatarCircles_->setChecked(settings_->avatarCircles()); decryptSidebar_->setChecked(settings_->decryptSidebar()); privacyScreen_->setChecked(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setChecked(settings_->onlyShareKeysWithVerifiedUsers()); shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers()); groupViewToggle_->setChecked(settings_->groupView()); timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline()); @@ -1008,10 +1028,14 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge formLayout_->addRow(new HorizontalLine{this}); boxWrap(tr("Device ID"), deviceIdValue_); boxWrap(tr("Device Fingerprint"), deviceFingerprintValue_); - boxWrap( - tr("Share keys with verified users and devices"), - shareKeysWithTrustedUsers_, - tr("Automatically replies to key requests from other users, if they are verified.")); + boxWrap(tr("Send encrypted messages to verified users only"), + onlyShareKeysWithVerifiedUsers_, + tr("Requires a user to be verified to send encrypted messages to them. This " + "improves safety but makes E2EE more tedious.")); + boxWrap(tr("Share keys with verified users and devices"), + shareKeysWithTrustedUsers_, + tr("Automatically replies to key requests from other users, if they are verified, " + "even if that device shouldn't have access to those keys otherwise.")); formLayout_->addRow(new HorizontalLine{this}); formLayout_->addRow(sessionKeysLabel, sessionKeysLayout); formLayout_->addRow(crossSigningKeysLabel, crossSigningKeysLayout); @@ -1179,6 +1203,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidge } }); + connect(onlyShareKeysWithVerifiedUsers_, &Toggle::toggled, this, [this](bool enabled) { + settings_->setOnlyShareKeysWithVerifiedUsers(enabled); + }); + connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) { settings_->setShareKeysWithTrustedUsers(enabled); }); @@ -1271,6 +1299,7 @@ UserSettingsPage::showEvent(QShowEvent *) groupViewToggle_->setState(settings_->groupView()); decryptSidebar_->setState(settings_->decryptSidebar()); privacyScreen_->setState(settings_->privacyScreen()); + onlyShareKeysWithVerifiedUsers_->setState(settings_->onlyShareKeysWithVerifiedUsers()); shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers()); avatarCircles_->setState(settings_->avatarCircles()); typingNotifications_->setState(settings_->typingNotifications()); diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index acb08569..096aab81 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -88,6 +88,8 @@ class UserSettings : public QObject setScreenShareHideCursor NOTIFY screenShareHideCursorChanged) Q_PROPERTY( bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged) + Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE + setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged) Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged) Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged) @@ -152,6 +154,7 @@ public: void setScreenShareRemoteVideo(bool state); void setScreenShareHideCursor(bool state); void setUseStunServer(bool state); + void setOnlyShareKeysWithVerifiedUsers(bool state); void setShareKeysWithTrustedUsers(bool state); void setProfile(QString profile); void setUserId(QString userId); @@ -208,6 +211,7 @@ public: bool screenShareHideCursor() const { return screenShareHideCursor_; } bool useStunServer() const { return useStunServer_; } bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; } + bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; } QString profile() const { return profile_; } QString userId() const { return userId_; } QString accessToken() const { return accessToken_; } @@ -252,6 +256,7 @@ signals: void screenShareRemoteVideoChanged(bool state); void screenShareHideCursorChanged(bool state); void useStunServerChanged(bool state); + void onlyShareKeysWithVerifiedUsersChanged(bool state); void shareKeysWithTrustedUsersChanged(bool state); void profileChanged(QString profile); void userIdChanged(QString userId); @@ -284,6 +289,7 @@ private: bool privacyScreen_; int privacyScreenTimeout_; bool shareKeysWithTrustedUsers_; + bool onlyShareKeysWithVerifiedUsers_; bool mobileMode_; int timelineMaxWidth_; int roomListWidth_; @@ -372,6 +378,7 @@ private: Toggle *privacyScreen_; QSpinBox *privacyScreenTimeout_; Toggle *shareKeysWithTrustedUsers_; + Toggle *onlyShareKeysWithVerifiedUsers_; Toggle *mobileMode_; QLabel *deviceFingerprintValue_; QLabel *deviceIdValue_; -- cgit 1.5.1 From 041d8fb56c435a3a5f5af9ff304f54deb5883c9b Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Wed, 21 Jul 2021 11:55:41 +0100 Subject: Reorganise src/RegisterPage.cpp --- src/RegisterPage.cpp | 548 ++++++++++++++++++++++++--------------------------- src/RegisterPage.h | 37 ++-- 2 files changed, 284 insertions(+), 301 deletions(-) diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 1588d07d..1d529f54 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "Config.h" #include "Logging.h" @@ -93,6 +94,7 @@ RegisterPage::RegisterPage(QWidget *parent) server_input_ = new TextField(); server_input_->setLabel(tr("Homeserver")); + server_input_->setRegexp(QRegularExpression("[a-z0-9.-]+")); server_input_->setToolTip( tr("A server that allows registration. Since matrix is decentralized, you need to first " "find a server you can register on or host your own.")); @@ -145,178 +147,39 @@ RegisterPage::RegisterPage(QWidget *parent) top_layout_->addLayout(button_layout_); top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter); top_layout_->addStretch(1); - - connect( - this, - &RegisterPage::versionErrorCb, - this, - [this](const QString &msg) { - error_server_label_->show(); - server_input_->setValid(false); - showError(error_server_label_, msg); - }, - Qt::QueuedConnection); + setLayout(top_layout_); connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked())); connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkFields); + connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername); connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkFields); + connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword); connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect( - password_confirmation_, &TextField::editingFinished, this, &RegisterPage::checkFields); + connect(password_confirmation_, + &TextField::editingFinished, + this, + &RegisterPage::checkPasswordConfirmation); connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click())); - connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkFields); - connect(this, &RegisterPage::registerErrorCb, this, [this](const QString &msg) { - showError(msg); - }); - connect( - this, - &RegisterPage::registrationFlow, - this, - [this](const std::string &user, - const std::string &pass, - const mtx::user_interactive::Unauthorized &unauthorized) { - auto completed_stages = unauthorized.completed; - auto flows = unauthorized.flows; - auto session = unauthorized.session.empty() ? http::client()->generate_txn_id() - : unauthorized.session; - - nhlog::ui()->info("Completed stages: {}", completed_stages.size()); - - if (!completed_stages.empty()) - flows.erase(std::remove_if( - flows.begin(), - flows.end(), - [completed_stages](auto flow) { - if (completed_stages.size() > flow.stages.size()) - return true; - for (size_t f = 0; f < completed_stages.size(); f++) - if (completed_stages[f] != flow.stages[f]) - return true; - return false; - }), - flows.end()); - - if (flows.empty()) { - nhlog::net()->error("No available registration flows!"); - emit registerErrorCb(tr("No supported registration flows!")); - return; - } - - auto current_stage = flows.front().stages.at(completed_stages.size()); - - if (current_stage == mtx::user_interactive::auth_types::recaptcha) { - auto captchaDialog = - new dialogs::ReCaptcha(QString::fromStdString(session), this); - - connect(captchaDialog, - &dialogs::ReCaptcha::confirmation, - this, - [this, user, pass, session, captchaDialog]() { - captchaDialog->close(); - captchaDialog->deleteLater(); - - emit registerAuth( - user, - pass, - mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); - connect(captchaDialog, - &dialogs::ReCaptcha::cancel, - this, - &RegisterPage::errorOccurred); - - QTimer::singleShot( - 1000, this, [captchaDialog]() { captchaDialog->show(); }); - } else if (current_stage == mtx::user_interactive::auth_types::dummy) { - emit registerAuth(user, - pass, - mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Dummy{}}); - } else { - // use fallback - auto dialog = - new dialogs::FallbackAuth(QString::fromStdString(current_stage), - QString::fromStdString(session), - this); - - connect(dialog, - &dialogs::FallbackAuth::confirmation, - this, - [this, user, pass, session, dialog]() { - dialog->close(); - dialog->deleteLater(); - - emit registerAuth( - user, - pass, - mtx::user_interactive::Auth{ - session, mtx::user_interactive::auth::Fallback{}}); - }); - connect(dialog, - &dialogs::FallbackAuth::cancel, - this, - &RegisterPage::errorOccurred); - - dialog->show(); - } - }); + connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer); connect( this, - &RegisterPage::registerAuth, + &RegisterPage::serverError, this, - [this](const std::string &user, - const std::string &pass, - const mtx::user_interactive::Auth &auth) { - http::client()->registration( - user, - pass, - auth, - [this, user, pass](const mtx::responses::Register &res, - mtx::http::RequestErr err) { - if (!err) { - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); - http::client()->set_device_id(res.device_id); - - emit registerOk(); - return; - } - - // The server requires registration flows. - if (err->status_code == 401) { - if (err->matrix_error.unauthorized.flows.empty()) { - nhlog::net()->warn( - "failed to retrieve registration flows: ({}) " - "{}", - static_cast(err->status_code), - err->matrix_error.error); - emit registerErrorCb( - QString::fromStdString(err->matrix_error.error)); - return; - } - - emit registrationFlow( - user, pass, err->matrix_error.unauthorized); - return; - } - - nhlog::net()->warn("failed to register: status_code ({}), " - "matrix_error: ({}), parser error ({})", - static_cast(err->status_code), - err->matrix_error.error, - err->parse_error); - - emit registerErrorCb(QString::fromStdString(err->matrix_error.error)); - }); - }); + [this](const QString &msg) { + server_input_->setValid(false); + showError(error_server_label_, msg); + }, + Qt::QueuedConnection); - setLayout(top_layout_); + connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup); + connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck); + connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration); + connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA); + connect( + this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth); } void @@ -345,191 +208,298 @@ RegisterPage::showError(QLabel *label, const QString &msg) int height = rect.height(); label->setFixedHeight((int)qCeil(width / 200.0) * height); label->setText(msg); + label->show(); } bool RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg) { if (t_field->isValid()) { - label->setText(""); label->hide(); return true; } else { - label->show(); showError(label, msg); return false; } } bool -RegisterPage::checkFields() +RegisterPage::checkUsername() { - error_label_->setText(""); - error_username_label_->setText(""); - error_password_label_->setText(""); - error_password_confirmation_label_->setText(""); - error_server_label_->setText(""); + return checkOneField(error_username_label_, + username_input_, + tr("The username must not be empty, and must contain only the " + "characters a-z, 0-9, ., _, =, -, and /.")); +} - error_username_label_->hide(); - error_password_label_->hide(); - error_password_confirmation_label_->hide(); - error_server_label_->hide(); +bool +RegisterPage::checkPassword() +{ + return checkOneField( + error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)")); +} - password_confirmation_->setValid(true); - server_input_->setValid(true); - - bool all_fields_good = true; - if (username_input_->isModified() && - !checkOneField(error_username_label_, - username_input_, - tr("The username must not be empty, and must contain only the " - "characters a-z, 0-9, ., _, =, -, and /."))) { - all_fields_good = false; - } else if (password_input_->isModified() && - !checkOneField(error_password_label_, - password_input_, - tr("Password is not long enough (min 8 chars)"))) { - all_fields_good = false; - } else if (password_confirmation_->isModified() && - password_input_->text() != password_confirmation_->text()) { - error_password_confirmation_label_->show(); +bool +RegisterPage::checkPasswordConfirmation() +{ + if (password_input_->text() == password_confirmation_->text()) { + error_password_confirmation_label_->hide(); + password_confirmation_->setValid(true); + return true; + } else { showError(error_password_confirmation_label_, tr("Passwords don't match")); password_confirmation_->setValid(false); - all_fields_good = false; - } else if (server_input_->isModified() && - (!server_input_->hasAcceptableInput() || server_input_->text().isEmpty())) { - error_server_label_->show(); - showError(error_server_label_, tr("Invalid server name")); - server_input_->setValid(false); - all_fields_good = false; - } - if (!username_input_->isModified() || !password_input_->isModified() || - !password_confirmation_->isModified() || !server_input_->isModified()) { - all_fields_good = false; + return false; } - return all_fields_good; +} + +bool +RegisterPage::checkServer() +{ + // This doesn't check that the server is reachable, + // just that the input is not obviously wrong. + return checkOneField(error_server_label_, server_input_, tr("Invalid server name")); } void RegisterPage::onRegisterButtonClicked() { - if (!checkFields()) { - showError(error_label_, - tr("One or more fields have invalid inputs. Please correct those issues " - "and try again.")); - return; - } else { - auto username = username_input_->text().toStdString(); - auto password = password_input_->text().toStdString(); - auto server = server_input_->text().toStdString(); + if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) { + auto server = server_input_->text().toStdString(); http::client()->set_server(server); http::client()->verify_certificates( !UserSettings::instance()->disableCertificateValidation()); - http::client()->well_known( - [this, username, password](const mtx::responses::WellKnown &res, - mtx::http::RequestErr err) { - if (err) { - if (err->status_code == 404) { - nhlog::net()->info("Autodiscovery: No .well-known."); - checkVersionAndRegister(username, password); - return; - } - - if (!err->parse_error.empty()) { - emit versionErrorCb(tr( - "Autodiscovery failed. Received malformed response.")); - nhlog::net()->error( - "Autodiscovery failed. Received malformed response."); - return; - } - - emit versionErrorCb(tr("Autodiscovery failed. Unknown error when " - "requesting .well-known.")); - nhlog::net()->error("Autodiscovery failed. Unknown error when " - "requesting .well-known. {} {}", - err->status_code, - err->error_code); + // This starts a chain of `emit`s which ends up doing the + // registration. Signals are used rather than normal function + // calls so that the dialogs used in UIA work correctly. + // + // The sequence of events looks something like this: + // + // dowellKnownLookup + // v + // doVersionsCheck + // v + // doRegistration + // v + // doUIA <-----------------+ + // v | More auth required + // doRegistrationWithAuth -+ + // | Success + // v + // registering + + emit wellKnownLookup(); + + emit registering(); + } +} + +void +RegisterPage::doWellKnownLookup() +{ + http::client()->well_known( + [this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) { + if (err) { + if (err->status_code == 404) { + nhlog::net()->info("Autodiscovery: No .well-known."); + // Check that the homeserver can be reached + emit versionsCheck(); return; } - nhlog::net()->info("Autodiscovery: Discovered '" + - res.homeserver.base_url + "'"); - http::client()->set_server(res.homeserver.base_url); - checkVersionAndRegister(username, password); - }); + if (!err->parse_error.empty()) { + emit serverError( + tr("Autodiscovery failed. Received malformed response.")); + nhlog::net()->error( + "Autodiscovery failed. Received malformed response."); + return; + } - emit registering(); - } + emit serverError(tr("Autodiscovery failed. Unknown error when " + "requesting .well-known.")); + nhlog::net()->error("Autodiscovery failed. Unknown error when " + "requesting .well-known. {} {}", + err->status_code, + err->error_code); + return; + } + + nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'"); + http::client()->set_server(res.homeserver.base_url); + // Check that the homeserver can be reached + emit versionsCheck(); + }); } void -RegisterPage::checkVersionAndRegister(const std::string &username, const std::string &password) +RegisterPage::doVersionsCheck() { + // Make a request to /_matrix/client/versions to check the address + // given is a Matrix homeserver. http::client()->versions( - [this, username, password](const mtx::responses::Versions &, mtx::http::RequestErr err) { + [this](const mtx::responses::Versions &, mtx::http::RequestErr err) { if (err) { if (err->status_code == 404) { - emit versionErrorCb(tr("The required endpoints were not found. " - "Possibly not a Matrix server.")); + emit serverError( + tr("The required endpoints were not found. Possibly " + "not a Matrix server.")); return; } if (!err->parse_error.empty()) { - emit versionErrorCb(tr("Received malformed response. Make sure " - "the homeserver domain is valid.")); + emit serverError( + tr("Received malformed response. Make sure the homeserver " + "domain is valid.")); return; } - emit versionErrorCb(tr( - "An unknown error occured. Make sure the homeserver domain is valid.")); + emit serverError(tr("An unknown error occured. Make sure the " + "homeserver domain is valid.")); return; } - http::client()->registration( - username, - password, - [this, username, password](const mtx::responses::Register &res, - mtx::http::RequestErr err) { - if (!err) { - http::client()->set_user(res.user_id); - http::client()->set_access_token(res.access_token); - - emit registerOk(); - return; - } - - // The server requires registration flows. - if (err->status_code == 401) { - if (err->matrix_error.unauthorized.flows.empty()) { - nhlog::net()->warn( - "failed to retrieve registration flows1: ({}) " - "{}", - static_cast(err->status_code), - err->matrix_error.error); - emit errorOccurred(); - emit registerErrorCb( - QString::fromStdString(err->matrix_error.error)); - return; - } - - emit registrationFlow( - username, password, err->matrix_error.unauthorized); - return; - } - - nhlog::net()->error( - "failed to register: status_code ({}), matrix_error({})", - static_cast(err->status_code), - err->matrix_error.error); - - emit registerErrorCb(QString::fromStdString(err->matrix_error.error)); - emit errorOccurred(); - }); + // Attempt registration without an `auth` dict + emit registration(); }); } +void +RegisterPage::doRegistration() +{ + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, registrationCb()); + } +} + +void +RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth) +{ + // These inputs should still be alright, but check just in case + if (checkUsername() && checkPassword() && checkPasswordConfirmation()) { + auto username = username_input_->text().toStdString(); + auto password = password_input_->text().toStdString(); + http::client()->registration(username, password, auth, registrationCb()); + } +} + +mtx::http::Callback +RegisterPage::registrationCb() +{ + // Return a function to be used as the callback when an attempt at + // registration is made. + return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) { + if (!err) { + http::client()->set_user(res.user_id); + http::client()->set_access_token(res.access_token); + emit registerOk(); + return; + } + + // The server requires registration flows. + if (err->status_code == 401) { + if (err->matrix_error.unauthorized.flows.empty()) { + nhlog::net()->warn("failed to retrieve registration flows: " + "status_code({}), matrix_error({}) ", + static_cast(err->status_code), + err->matrix_error.error); + showError(QString::fromStdString(err->matrix_error.error)); + return; + } + + // Attempt to complete a UIA stage + emit UIA(err->matrix_error.unauthorized); + return; + } + + nhlog::net()->error("failed to register: status_code ({}), matrix_error({})", + static_cast(err->status_code), + err->matrix_error.error); + + showError(QString::fromStdString(err->matrix_error.error)); + }; +} + +void +RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized) +{ + auto completed_stages = unauthorized.completed; + auto flows = unauthorized.flows; + auto session = + unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session; + + nhlog::ui()->info("Completed stages: {}", completed_stages.size()); + + if (!completed_stages.empty()) { + // Get rid of all flows which don't start with the sequence of + // stages that have already been completed. + flows.erase( + std::remove_if(flows.begin(), + flows.end(), + [completed_stages](auto flow) { + if (completed_stages.size() > flow.stages.size()) + return true; + for (size_t f = 0; f < completed_stages.size(); f++) + if (completed_stages[f] != flow.stages[f]) + return true; + return false; + }), + flows.end()); + } + + if (flows.empty()) { + nhlog::ui()->error("No available registration flows!"); + showError(tr("No supported registration flows!")); + return; + } + + auto current_stage = flows.front().stages.at(completed_stages.size()); + + if (current_stage == mtx::user_interactive::auth_types::recaptcha) { + auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this); + + connect(captchaDialog, + &dialogs::ReCaptcha::confirmation, + this, + [this, session, captchaDialog]() { + captchaDialog->close(); + captchaDialog->deleteLater(); + doRegistrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::Fallback{}}); + }); + + connect( + captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred); + + QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); }); + + } else if (current_stage == mtx::user_interactive::auth_types::dummy) { + doRegistrationWithAuth( + mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}}); + + } else { + // use fallback + auto dialog = new dialogs::FallbackAuth( + QString::fromStdString(current_stage), QString::fromStdString(session), this); + + connect( + dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() { + dialog->close(); + dialog->deleteLater(); + emit registrationWithAuth(mtx::user_interactive::Auth{ + session, mtx::user_interactive::auth::Fallback{}}); + }); + + connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred); + + dialog->show(); + } +} + void RegisterPage::paintEvent(QPaintEvent *) { diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 0e4a45d0..44128939 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -10,6 +10,7 @@ #include #include +#include class FlatButton; class RaisedButton; @@ -33,30 +34,40 @@ signals: void errorOccurred(); //! Used to trigger the corresponding slot outside of the main thread. - void versionErrorCb(const QString &err); + void serverError(const QString &err); + + void wellKnownLookup(); + void versionsCheck(); + void registration(); + void UIA(const mtx::user_interactive::Unauthorized &unauthorized); + void registrationWithAuth(const mtx::user_interactive::Auth &auth); void registering(); void registerOk(); - void registerErrorCb(const QString &msg); - void registrationFlow(const std::string &user, - const std::string &pass, - const mtx::user_interactive::Unauthorized &unauthorized); - void registerAuth(const std::string &user, - const std::string &pass, - const mtx::user_interactive::Auth &auth); private slots: void onBackButtonClicked(); void onRegisterButtonClicked(); +private: // function for showing different errors void showError(const QString &msg); + void showError(QLabel *label, const QString &msg); -private: bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg); - bool checkFields(); - void showError(QLabel *label, const QString &msg); - void checkVersionAndRegister(const std::string &username, const std::string &password); + bool checkUsername(); + bool checkPassword(); + bool checkPasswordConfirmation(); + bool checkServer(); + + void doWellKnownLookup(); + void doVersionsCheck(); + void doRegistration(); + void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); + void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); + mtx::http::Callback registrationCb(); + void completeUiaStage(const mtx::user_interactive::Unauthorized &unauthorized); + QVBoxLayout *top_layout_; QHBoxLayout *back_layout_; @@ -69,6 +80,7 @@ private: QLabel *error_password_label_; QLabel *error_password_confirmation_label_; QLabel *error_server_label_; + QLabel *error_registration_token_label_; FlatButton *back_button_; RaisedButton *register_button_; @@ -81,4 +93,5 @@ private: TextField *password_input_; TextField *password_confirmation_; TextField *server_input_; + TextField *registration_token_input_; }; -- cgit 1.5.1 From 87e81498b73d59e8173953079ca5d0d73c5c302f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 4 Aug 2021 02:27:50 +0200 Subject: Fix window placement on wayland and add close buttons We explicitly set a parent. We can't assign to ApplicationWindow.transientParent though, only to Window.transientParent, so we just call setTransientParent in C++. --- resources/qml/InviteDialog.qml | 4 ++-- resources/qml/MessageInput.qml | 2 +- resources/qml/MessageView.qml | 2 +- resources/qml/RawMessageDialog.qml | 7 ++++--- resources/qml/ReadReceipts.qml | 5 ++--- resources/qml/RoomMembers.qml | 6 +++--- resources/qml/RoomSettings.qml | 7 +++---- resources/qml/Root.qml | 6 +++--- resources/qml/TimelineRow.qml | 2 +- resources/qml/TimelineView.qml | 2 +- resources/qml/UserProfile.qml | 14 ++++++++------ resources/qml/delegates/Reply.qml | 2 +- resources/qml/device-verification/DeviceVerification.qml | 7 +++---- resources/qml/dialogs/ImagePackSettingsDialog.qml | 11 ++++++++--- resources/qml/dialogs/InputDialog.qml | 1 + src/ui/NhekoGlobalObject.cpp | 7 +++++++ src/ui/NhekoGlobalObject.h | 3 +++ 17 files changed, 52 insertions(+), 36 deletions(-) diff --git a/resources/qml/InviteDialog.qml b/resources/qml/InviteDialog.qml index 50287ad5..2c0e15a7 100644 --- a/resources/qml/InviteDialog.qml +++ b/resources/qml/InviteDialog.qml @@ -30,12 +30,12 @@ ApplicationWindow { } title: qsTr("Invite users to %1").arg(plainRoomName) - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 width: 340 palette: Nheko.colors color: Nheko.colors.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(inviteDialogRoot) Shortcut { sequence: "Ctrl+Enter" diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 8bc8ac62..7fb09684 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -7,7 +7,7 @@ import "./voip" import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 +import QtQuick.Window 2.13 import im.nheko 1.0 Rectangle { diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 9ba5e2d0..f3e15d84 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -10,7 +10,7 @@ import QtGraphicalEffects 1.0 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 +import QtQuick.Window 2.13 import im.nheko 1.0 ScrollView { diff --git a/resources/qml/RawMessageDialog.qml b/resources/qml/RawMessageDialog.qml index 231e2f6d..e2a476cd 100644 --- a/resources/qml/RawMessageDialog.qml +++ b/resources/qml/RawMessageDialog.qml @@ -11,13 +11,12 @@ ApplicationWindow { property alias rawMessage: rawMessageView.text - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 420 width: 420 palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Tool | Qt.WindowStaysOnTopHint + flags: Qt.Tool | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(rawMessageRoot) Shortcut { sequence: StandardKey.Cancel @@ -40,6 +39,7 @@ ApplicationWindow { background: Rectangle { color: Nheko.colors.base } + } } @@ -48,4 +48,5 @@ ApplicationWindow { standardButtons: DialogButtonBox.Ok onAccepted: rawMessageRoot.close() } + } diff --git a/resources/qml/ReadReceipts.qml b/resources/qml/ReadReceipts.qml index 8869d813..9adbfd5c 100644 --- a/resources/qml/ReadReceipts.qml +++ b/resources/qml/ReadReceipts.qml @@ -13,15 +13,14 @@ ApplicationWindow { property ReadReceiptsProxy readReceipts property Room room - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 380 width: 340 minimumHeight: 380 minimumWidth: headerTitle.width + 2 * Nheko.paddingMedium palette: Nheko.colors color: Nheko.colors.window - flags: Qt.Dialog + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(readReceiptsRoot) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/RoomMembers.qml b/resources/qml/RoomMembers.qml index 641a08be..447e6fd1 100644 --- a/resources/qml/RoomMembers.qml +++ b/resources/qml/RoomMembers.qml @@ -6,7 +6,7 @@ import "./ui" import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 -import QtQuick.Window 2.12 +import QtQuick.Window 2.13 import im.nheko 1.0 ApplicationWindow { @@ -15,13 +15,13 @@ ApplicationWindow { property MemberList members title: qsTr("Members of %1").arg(members.roomName) - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 650 width: 420 minimumHeight: 420 palette: Nheko.colors color: Nheko.colors.window + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(roomMembersRoot) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index b8e527a5..6ba080c4 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -7,7 +7,7 @@ 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.3 +import QtQuick.Window 2.13 import im.nheko 1.0 ApplicationWindow { @@ -15,14 +15,13 @@ ApplicationWindow { property var roomSettings - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) minimumWidth: 420 minimumHeight: 650 palette: Nheko.colors color: Nheko.colors.window modality: Qt.NonModal - flags: Qt.Dialog + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(roomSettingsDialog) title: qsTr("Room Settings") Shortcut { diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 70cfbda5..b229acda 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -9,10 +9,10 @@ import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform import QtGraphicalEffects 1.0 -import QtQuick 2.9 -import QtQuick.Controls 2.5 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 -import QtQuick.Window 2.2 +import QtQuick.Window 2.15 import im.nheko 1.0 import im.nheko.EmojiModel 1.0 diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 755ab503..6345f44c 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -7,7 +7,7 @@ import "./emoji" import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 +import QtQuick.Window 2.13 import im.nheko 1.0 Item { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index e4036eb7..6fc9d51b 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -13,7 +13,7 @@ import QtGraphicalEffects 1.0 import QtQuick 2.9 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 -import QtQuick.Window 2.2 +import QtQuick.Window 2.13 import im.nheko 1.0 import im.nheko.EmojiModel 1.0 diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index d138060b..767d2317 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -4,19 +4,20 @@ import "./device-verification" import "./ui" -import QtQuick 2.9 -import QtQuick.Controls 2.3 +import QtQuick 2.15 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 -import QtQuick.Window 2.3 +import QtQuick.Window 2.13 import im.nheko 1.0 ApplicationWindow { + // this does not work in ApplicationWindow, just in Window + //transientParent: Nheko.mainwindow() + id: userProfileDialog property var profile - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 650 width: 420 minimumHeight: 420 @@ -24,7 +25,8 @@ ApplicationWindow { color: Nheko.colors.window title: profile.isGlobalUserProfile ? qsTr("Global User Profile") : qsTr("Room User Profile") modality: Qt.NonModal - flags: Qt.Dialog + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(userProfileDialog) Shortcut { sequence: StandardKey.Cancel diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 75e3d617..3e02a940 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -5,7 +5,7 @@ import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 -import QtQuick.Window 2.2 +import QtQuick.Window 2.13 import im.nheko 1.0 Item { diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml index e2c66c5a..8e0271d6 100644 --- a/resources/qml/device-verification/DeviceVerification.qml +++ b/resources/qml/device-verification/DeviceVerification.qml @@ -4,7 +4,7 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 -import QtQuick.Window 2.10 +import QtQuick.Window 2.13 import im.nheko 1.0 ApplicationWindow { @@ -14,13 +14,12 @@ ApplicationWindow { onClosing: TimelineManager.removeVerificationFlow(flow) title: stack.currentItem.title - flags: Qt.Dialog modality: Qt.NonModal palette: Nheko.colors height: stack.implicitHeight width: stack.implicitWidth - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(dialog) StackView { id: stack diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index c4b4a885..3d830bf7 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -20,14 +20,13 @@ ApplicationWindow { readonly property int stickerDimPad: 128 + Nheko.paddingSmall title: qsTr("Image pack settings") - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) - y: MainWindow.y + (MainWindow.height / 2) - (height / 2) height: 400 width: 600 palette: Nheko.colors color: Nheko.colors.base modality: Qt.NonModal - flags: Qt.Dialog + flags: Qt.Dialog | Qt.WindowCloseButtonHint + Component.onCompleted: Nheko.reparent(win) AdaptiveLayout { id: adaptiveView @@ -202,6 +201,12 @@ ApplicationWindow { color: Nheko.colors.window ColumnLayout { + //Button { + // Layout.alignment: Qt.AlignHCenter + // text: qsTr("Edit") + // enabled: currentPack.canEdit + //} + id: packinfo property string packName: currentPack ? currentPack.packname : "" diff --git a/resources/qml/dialogs/InputDialog.qml b/resources/qml/dialogs/InputDialog.qml index 134b78a3..e0f17851 100644 --- a/resources/qml/dialogs/InputDialog.qml +++ b/resources/qml/dialogs/InputDialog.qml @@ -16,6 +16,7 @@ ApplicationWindow { modality: Qt.NonModal flags: Qt.Dialog + Component.onCompleted: Nheko.reparent(inputDialog) width: 350 height: fontMetrics.lineSpacing * 7 diff --git a/src/ui/NhekoGlobalObject.cpp b/src/ui/NhekoGlobalObject.cpp index fea10839..9e0d706b 100644 --- a/src/ui/NhekoGlobalObject.cpp +++ b/src/ui/NhekoGlobalObject.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "Cache_p.h" #include "ChatPage.h" @@ -140,3 +141,9 @@ Nheko::openJoinRoomDialog() const MainWindow::instance()->openJoinRoomDialog( [](const QString &room_id) { ChatPage::instance()->joinRoom(room_id); }); } + +void +Nheko::reparent(QWindow *win) const +{ + win->setTransientParent(MainWindow::instance()->windowHandle()); +} diff --git a/src/ui/NhekoGlobalObject.h b/src/ui/NhekoGlobalObject.h index cfe982c5..d4d119dc 100644 --- a/src/ui/NhekoGlobalObject.h +++ b/src/ui/NhekoGlobalObject.h @@ -11,6 +11,8 @@ #include "Theme.h" #include "UserProfile.h" +class QWindow; + class Nheko : public QObject { Q_OBJECT @@ -49,6 +51,7 @@ public: Q_INVOKABLE void openLogoutDialog() const; Q_INVOKABLE void openCreateRoomDialog() const; Q_INVOKABLE void openJoinRoomDialog() const; + Q_INVOKABLE void reparent(QWindow *win) const; public slots: void updateUserProfile(); -- cgit 1.5.1 From 571ae3d51b70d4e71b54fae929ad1e41a5fac06c Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Wed, 4 Aug 2021 18:00:37 -0400 Subject: Disable brew in macos CI --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7ff92c17..d551f2a3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,9 +52,9 @@ build-macos: stage: build tags: [macos] before_script: - - brew update - - brew reinstall --force python3 - - brew bundle --file=./.ci/macos/Brewfile --force --cleanup + #- brew update + #- brew reinstall --force python3 + #- brew bundle --file=./.ci/macos/Brewfile --force --cleanup - pip3 install dmgbuild - rm -rf ../.hunter && mv .hunter ../.hunter || true script: -- cgit 1.5.1 From bb6a57644c46dec6d4fc9f8e30839677ae278fb2 Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Thu, 5 Aug 2021 16:12:36 +0100 Subject: Make things private slots --- src/RegisterPage.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/RegisterPage.h b/src/RegisterPage.h index 44128939..42ea00cb 100644 --- a/src/RegisterPage.h +++ b/src/RegisterPage.h @@ -49,7 +49,6 @@ private slots: void onBackButtonClicked(); void onRegisterButtonClicked(); -private: // function for showing different errors void showError(const QString &msg); void showError(QLabel *label, const QString &msg); @@ -66,8 +65,8 @@ private: void doUIA(const mtx::user_interactive::Unauthorized &unauthorized); void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth); mtx::http::Callback registrationCb(); - void completeUiaStage(const mtx::user_interactive::Unauthorized &unauthorized); +private: QVBoxLayout *top_layout_; QHBoxLayout *back_layout_; -- cgit 1.5.1 From bd31726f2ff103dda7c84b8a6e801d15404ac96b Mon Sep 17 00:00:00 2001 From: Callum Brown Date: Thu, 5 Aug 2021 16:41:40 +0100 Subject: Allow all characters when checking server input So IDNs are not rejected. Invalid server names will be caught later. --- src/RegisterPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 1d529f54..bae24df0 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -94,7 +94,7 @@ RegisterPage::RegisterPage(QWidget *parent) server_input_ = new TextField(); server_input_->setLabel(tr("Homeserver")); - server_input_->setRegexp(QRegularExpression("[a-z0-9.-]+")); + server_input_->setRegexp(QRegularExpression(".+")); server_input_->setToolTip( tr("A server that allows registration. Since matrix is decentralized, you need to first " "find a server you can register on or host your own.")); -- cgit 1.5.1 From f7d1d1b9416248bb75bbacc3fa14356a3acc9f24 Mon Sep 17 00:00:00 2001 From: Loren Burkholder Date: Tue, 3 Aug 2021 07:20:36 -0400 Subject: Open profile when clicking avatar --- resources/qml/RoomList.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml index cbc65fc0..98532606 100644 --- a/resources/qml/RoomList.qml +++ b/resources/qml/RoomList.qml @@ -439,6 +439,7 @@ Page { url: (userInfoGrid.profile ? userInfoGrid.profile.avatarUrl : "").replace("mxc://", "image://MxcImage/") displayName: userInfoGrid.profile ? userInfoGrid.profile.displayName : "" userid: userInfoGrid.profile ? userInfoGrid.profile.userid : "" + enabled: false } ColumnLayout { -- cgit 1.5.1 From b156dd51cb8a393ab83af936767e95ce8379e157 Mon Sep 17 00:00:00 2001 From: Joseph Donofry Date: Thu, 5 Aug 2021 22:22:47 -0400 Subject: Update qt5 path after brew changes --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d551f2a3..e24c5c85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -58,8 +58,8 @@ build-macos: - pip3 install dmgbuild - rm -rf ../.hunter && mv .hunter ../.hunter || true script: - - export PATH=/usr/local/opt/qt/bin/:${PATH} - - export CMAKE_PREFIX_PATH=/usr/local/opt/qt5 + - export PATH=/usr/local/opt/qt@5/bin/:${PATH} + - export CMAKE_PREFIX_PATH=/usr/local/opt/qt@5 - cmake -GNinja -H. -Bbuild -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=.deps/usr -- cgit 1.5.1 From a57a15a2e07da8cc07bc12e828b7c636efe36cbc Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 01:45:47 +0200 Subject: Basic sticker pack editor --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- resources/qml/Avatar.qml | 6 +- resources/qml/RoomSettings.qml | 4 +- resources/qml/ScrollHelper.qml | 7 +- resources/qml/components/AvatarListTile.qml | 133 ++++++++++ resources/qml/dialogs/ImagePackEditorDialog.qml | 283 ++++++++++++++++++++++ resources/qml/dialogs/ImagePackSettingsDialog.qml | 174 ++++--------- resources/res.qrc | 2 + src/Cache.cpp | 2 +- src/Cache_p.h | 34 ++- src/MxcImageProvider.cpp | 26 +- src/MxcImageProvider.h | 7 +- src/SingleImagePackModel.cpp | 181 ++++++++++++++ src/SingleImagePackModel.h | 38 ++- src/timeline/TimelineModel.cpp | 9 + src/timeline/TimelineModel.h | 8 +- 17 files changed, 751 insertions(+), 167 deletions(-) create mode 100644 resources/qml/components/AvatarListTile.qml create mode 100644 resources/qml/dialogs/ImagePackEditorDialog.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f824048..e8bc855d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb + GIT_TAG e5688a2c5987a614b5055595f991f18568127bd2 ) 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 0fa450b3..2c0c5ebf 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -161,7 +161,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: 316a4040785ee2eabac7ef5ce7b4acb71c48f6eb + - commit: e5688a2c5987a614b5055595f991f18568127bd2 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 6c12952a..9685dde1 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -11,10 +11,11 @@ import im.nheko 1.0 Rectangle { id: avatar - property alias url: img.source + property string url property string userid property string displayName property alias textColor: label.color + property bool crop: true signal clicked(var mouse) @@ -44,12 +45,13 @@ Rectangle { anchors.fill: parent asynchronous: true - fillMode: Image.PreserveAspectCrop + fillMode: avatar.crop ? Image.PreserveAspectCrop : Image.PreserveAspectFit mipmap: true smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height layer.enabled: true + source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale") MouseArea { id: mouseArea diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 6ba080c4..69cf427c 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -154,7 +154,7 @@ ApplicationWindow { GridLayout { columns: 2 - rowSpacing: 10 + rowSpacing: Nheko.paddingLarge MatrixText { text: qsTr("SETTINGS") @@ -180,7 +180,7 @@ ApplicationWindow { } MatrixText { - text: "Room access" + text: qsTr("Room access") Layout.fillWidth: true } diff --git a/resources/qml/ScrollHelper.qml b/resources/qml/ScrollHelper.qml index 2dd56f27..e584ae3d 100644 --- a/resources/qml/ScrollHelper.qml +++ b/resources/qml/ScrollHelper.qml @@ -30,6 +30,10 @@ MouseArea { property alias enabled: root.enabled function calculateNewPosition(flickableItem, wheel) { + // breaks ListView's with headers... + //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) + // minYExtent += flickableItem.headerItem.height; + //Nothing to scroll if (flickableItem.contentHeight < flickableItem.height) return flickableItem.contentY; @@ -55,9 +59,6 @@ MouseArea { var minYExtent = flickableItem.originY + flickableItem.topMargin; var maxYExtent = (flickableItem.contentHeight + flickableItem.bottomMargin + flickableItem.originY) - flickableItem.height; - if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) - minYExtent += flickableItem.headerItem.height; - //Avoid overscrolling return Math.max(minYExtent, Math.min(maxYExtent, flickableItem.contentY - pixelDelta)); } diff --git a/resources/qml/components/AvatarListTile.qml b/resources/qml/components/AvatarListTile.qml new file mode 100644 index 00000000..36c26a97 --- /dev/null +++ b/resources/qml/components/AvatarListTile.qml @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import im.nheko 1.0 + +Rectangle { + id: tile + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property color bubbleBackground: Nheko.colors.highlight + property color bubbleText: Nheko.colors.highlightedText + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + required property string avatarUrl + required property string title + required property string subtitle + required property int index + required property int selectedIndex + property bool crop: true + + color: background + height: avatarSize + 2 * Nheko.paddingMedium + width: ListView.view.width + state: "normal" + states: [ + State { + name: "highlight" + when: hovered.hovered && !(index == selectedIndex) + + PropertyChanges { + target: tile + background: Nheko.colors.dark + importantText: Nheko.colors.brightText + unimportantText: Nheko.colors.brightText + bubbleBackground: Nheko.colors.highlight + bubbleText: Nheko.colors.highlightedText + } + + }, + State { + name: "selected" + when: index == selectedIndex + + PropertyChanges { + target: tile + background: Nheko.colors.highlight + importantText: Nheko.colors.highlightedText + unimportantText: Nheko.colors.highlightedText + bubbleBackground: Nheko.colors.highlightedText + bubbleText: Nheko.colors.highlight + } + + } + ] + + HoverHandler { + id: hovered + } + + RowLayout { + spacing: Nheko.paddingMedium + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + + Avatar { + id: avatar + + enabled: false + Layout.alignment: Qt.AlignVCenter + height: avatarSize + width: avatarSize + url: tile.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: title + crop: tile.crop + } + + ColumnLayout { + id: textContent + + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.minimumWidth: 100 + width: parent.width - avatar.width + Layout.preferredWidth: parent.width - avatar.width + spacing: Nheko.paddingSmall + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + Layout.alignment: Qt.AlignBottom + color: tile.importantText + elideWidth: textContent.width - Nheko.paddingMedium + fullText: title + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + RowLayout { + Layout.fillWidth: true + spacing: 0 + + ElidedLabel { + color: tile.unimportantText + font.pixelSize: fontMetrics.font.pixelSize * 0.9 + elideWidth: textContent.width - Nheko.paddingSmall + fullText: subtitle + textFormat: Text.PlainText + } + + Item { + Layout.fillWidth: true + } + + } + + } + + } + +} diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml new file mode 100644 index 00000000..0049d3b4 --- /dev/null +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import "../components" +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import im.nheko 1.0 + +ApplicationWindow { + //Component.onCompleted: Nheko.reparent(win) + + id: win + + property int avatarSize: Math.ceil(fontMetrics.lineSpacing * 2.3) + property SingleImagePackModel imagePack + property int currentImageIndex: -1 + readonly property int stickerDim: 128 + readonly property int stickerDimPad: 128 + Nheko.paddingSmall + + title: qsTr("Editing image pack") + height: 600 + width: 600 + palette: Nheko.colors + color: Nheko.colors.base + modality: Qt.WindowModal + flags: Qt.Dialog | Qt.WindowCloseButtonHint + + AdaptiveLayout { + id: adaptiveView + + anchors.fill: parent + singlePageMode: false + pageIndex: 0 + + AdaptiveLayoutElement { + id: packlistC + + visible: Settings.groupView + minimumWidth: 200 + collapsedWidth: 200 + preferredWidth: 300 + maximumWidth: 300 + clip: true + + ListView { + //required property bool isEmote + //required property bool isSticker + + model: imagePack + + ScrollHelper { + flickable: parent + anchors.fill: parent + enabled: !Settings.mobileMode + } + + header: AvatarListTile { + title: imagePack.packname + avatarUrl: imagePack.avatarUrl + subtitle: imagePack.statekey + index: -1 + selectedIndex: currentImageIndex + + TapHandler { + onSingleTapped: currentImageIndex = -1 + } + + Rectangle { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + height: parent.height - Nheko.paddingSmall * 2 + width: 3 + color: Nheko.colors.highlight + } + + } + + delegate: AvatarListTile { + id: packItem + + property color background: Nheko.colors.window + property color importantText: Nheko.colors.text + property color unimportantText: Nheko.colors.buttonText + property color bubbleBackground: Nheko.colors.highlight + property color bubbleText: Nheko.colors.highlightedText + required property string shortCode + required property string url + required property string body + + title: shortCode + subtitle: body + avatarUrl: url + selectedIndex: currentImageIndex + crop: false + + TapHandler { + onSingleTapped: currentImageIndex = index + } + + } + + } + + } + + AdaptiveLayoutElement { + id: packinfoC + + Rectangle { + color: Nheko.colors.window + + GridLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + visible: currentImageIndex == -1 + enabled: visible + columns: 2 + rowSpacing: Nheko.paddingLarge + + Avatar { + Layout.columnSpan: 2 + url: imagePack.avatarUrl.replace("mxc://", "image://MxcImage/") + displayName: imagePack.packname + height: 130 + width: 130 + crop: false + Layout.alignment: Qt.AlignHCenter + } + + MatrixText { + visible: imagePack.roomid + text: qsTr("State key") + } + + MatrixTextField { + visible: imagePack.roomid + Layout.fillWidth: true + text: imagePack.statekey + onTextEdited: imagePack.statekey = text + } + + MatrixText { + text: qsTr("Packname") + } + + MatrixTextField { + Layout.fillWidth: true + text: imagePack.packname + onTextEdited: imagePack.packname = text + } + + MatrixText { + text: qsTr("Attrbution") + } + + MatrixTextField { + Layout.fillWidth: true + text: imagePack.attribution + onTextEdited: imagePack.attribution = text + } + + MatrixText { + text: qsTr("Use as Emoji") + } + + ToggleButton { + checked: imagePack.isEmotePack + onToggled: imagePack.isEmotePack = checked + Layout.alignment: Qt.AlignRight + } + + MatrixText { + text: qsTr("Use as Sticker") + } + + ToggleButton { + checked: imagePack.isStickerPack + onToggled: imagePack.isStickerPack = checked + Layout.alignment: Qt.AlignRight + } + + Item { + Layout.columnSpan: 2 + Layout.fillHeight: true + } + + } + + GridLayout { + anchors.fill: parent + anchors.margins: Nheko.paddingMedium + visible: currentImageIndex >= 0 + enabled: visible + columns: 2 + rowSpacing: Nheko.paddingLarge + + Avatar { + Layout.columnSpan: 2 + url: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Url).replace("mxc://", "image://MxcImage/") + displayName: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + height: 130 + width: 130 + crop: false + Layout.alignment: Qt.AlignHCenter + } + + MatrixText { + text: qsTr("Shortcode") + } + + MatrixTextField { + Layout.fillWidth: true + text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.ShortCode) + onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.ShortCode) + } + + MatrixText { + text: qsTr("Body") + } + + MatrixTextField { + Layout.fillWidth: true + text: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.Body) + onTextEdited: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.Body) + } + + MatrixText { + text: qsTr("Use as Emoji") + } + + ToggleButton { + checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) + onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsEmote) + Layout.alignment: Qt.AlignRight + } + + MatrixText { + text: qsTr("Use as Sticker") + } + + ToggleButton { + checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) + onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsSticker) + Layout.alignment: Qt.AlignRight + } + + Item { + Layout.columnSpan: 2 + Layout.fillHeight: true + } + + } + + } + + } + + } + + footer: DialogButtonBox { + id: buttons + + Button { + text: qsTr("Cancel") + DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole + onClicked: win.close() + } + + Button { + text: qsTr("Save") + DialogButtonBox.buttonRole: DialogButtonBox.ApplyRole + onClicked: { + imagePack.save(); + win.close(); + } + } + + } + +} diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index 3d830bf7..c57867fd 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -20,14 +20,22 @@ ApplicationWindow { readonly property int stickerDimPad: 128 + Nheko.paddingSmall title: qsTr("Image pack settings") - height: 400 - width: 600 + height: 600 + width: 800 palette: Nheko.colors color: Nheko.colors.base modality: Qt.NonModal flags: Qt.Dialog | Qt.WindowCloseButtonHint Component.onCompleted: Nheko.reparent(win) + Component { + id: packEditor + + ImagePackEditorDialog { + } + + } + AdaptiveLayout { id: adaptiveView @@ -54,7 +62,7 @@ ApplicationWindow { enabled: !Settings.mobileMode } - delegate: Rectangle { + delegate: AvatarListTile { id: packItem property color background: Nheko.colors.window @@ -63,131 +71,24 @@ ApplicationWindow { property color bubbleBackground: Nheko.colors.highlight property color bubbleText: Nheko.colors.highlightedText required property string displayName - required property string avatarUrl required property bool fromAccountData required property bool fromCurrentRoom - required property int index - - color: background - height: avatarSize + 2 * Nheko.paddingMedium - width: ListView.view.width - state: "normal" - states: [ - State { - name: "highlight" - when: hovered.hovered && !(index == currentPackIndex) - - PropertyChanges { - target: packItem - background: Nheko.colors.dark - importantText: Nheko.colors.brightText - unimportantText: Nheko.colors.brightText - bubbleBackground: Nheko.colors.highlight - bubbleText: Nheko.colors.highlightedText - } - - }, - State { - name: "selected" - when: index == currentPackIndex - - PropertyChanges { - target: packItem - background: Nheko.colors.highlight - importantText: Nheko.colors.highlightedText - unimportantText: Nheko.colors.highlightedText - bubbleBackground: Nheko.colors.highlightedText - bubbleText: Nheko.colors.highlight - } - } - ] + title: displayName + subtitle: { + if (fromAccountData) + return qsTr("Private pack"); + else if (fromCurrentRoom) + return qsTr("Pack from this room"); + else + return qsTr("Globally enabled pack"); + } + selectedIndex: currentPackIndex TapHandler { - margin: -Nheko.paddingSmall onSingleTapped: currentPackIndex = index } - HoverHandler { - id: hovered - } - - RowLayout { - spacing: Nheko.paddingMedium - anchors.fill: parent - anchors.margins: Nheko.paddingMedium - - Avatar { - // In the future we could show an online indicator by setting the userid for the avatar - //userid: Nheko.currentUser.userid - - id: avatar - - enabled: false - Layout.alignment: Qt.AlignVCenter - height: avatarSize - width: avatarSize - url: avatarUrl.replace("mxc://", "image://MxcImage/") - displayName: packItem.displayName - } - - ColumnLayout { - id: textContent - - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - Layout.minimumWidth: 100 - width: parent.width - avatar.width - Layout.preferredWidth: parent.width - avatar.width - spacing: Nheko.paddingSmall - - RowLayout { - Layout.fillWidth: true - spacing: 0 - - ElidedLabel { - Layout.alignment: Qt.AlignBottom - color: packItem.importantText - elideWidth: textContent.width - Nheko.paddingMedium - fullText: displayName - textFormat: Text.PlainText - } - - Item { - Layout.fillWidth: true - } - - } - - RowLayout { - Layout.fillWidth: true - spacing: 0 - - ElidedLabel { - color: packItem.unimportantText - font.pixelSize: fontMetrics.font.pixelSize * 0.9 - elideWidth: textContent.width - Nheko.paddingSmall - fullText: { - if (fromAccountData) - return qsTr("Private pack"); - else if (fromCurrentRoom) - return qsTr("Pack from this room"); - else - return qsTr("Globally enabled pack"); - } - textFormat: Text.PlainText - } - - Item { - Layout.fillWidth: true - } - - } - - } - - } - } } @@ -201,15 +102,10 @@ ApplicationWindow { color: Nheko.colors.window ColumnLayout { - //Button { - // Layout.alignment: Qt.AlignHCenter - // text: qsTr("Edit") - // enabled: currentPack.canEdit - //} - id: packinfo property string packName: currentPack ? currentPack.packname : "" + property string attribution: currentPack ? currentPack.attribution : "" property string avatarUrl: currentPack ? currentPack.avatarUrl : "" anchors.fill: parent @@ -227,8 +123,18 @@ ApplicationWindow { MatrixText { text: packinfo.packName - font.pixelSize: 24 + font.pixelSize: Math.ceil(fontMetrics.pixelSize * 1.1) + horizontalAlignment: TextEdit.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 + } + + MatrixText { + text: packinfo.attribution + wrapMode: TextEdit.Wrap + horizontalAlignment: TextEdit.AlignHCenter Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: packinfoC.width - Nheko.paddingLarge * 2 } GridLayout { @@ -250,6 +156,18 @@ ApplicationWindow { } + Button { + Layout.alignment: Qt.AlignHCenter + text: qsTr("Edit") + enabled: currentPack.canEdit + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": currentPack + }); + dialog.show(); + } + } + GridView { Layout.fillHeight: true Layout.fillWidth: true @@ -272,7 +190,7 @@ ApplicationWindow { width: stickerDim height: stickerDim hoverEnabled: true - ToolTip.text: ":" + model.shortcode + ": - " + model.body + ToolTip.text: ":" + model.shortCode + ": - " + model.body ToolTip.visible: hovered contentItem: Image { diff --git a/resources/res.qrc b/resources/res.qrc index c911653c..d7187f42 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -160,6 +160,7 @@ qml/device-verification/Success.qml qml/dialogs/InputDialog.qml qml/dialogs/ImagePackSettingsDialog.qml + qml/dialogs/ImagePackEditorDialog.qml qml/ui/Ripple.qml qml/ui/Spinner.qml qml/ui/animations/BlinkAnimation.qml @@ -173,6 +174,7 @@ qml/voip/VideoCall.qml qml/components/AdaptiveLayout.qml qml/components/AdaptiveLayoutElement.qml + qml/components/AvatarListTile.qml qml/components/FlatButton.qml qml/RoomMembers.qml qml/InviteDialog.qml diff --git a/src/Cache.cpp b/src/Cache.cpp index 291df053..f3f3dbb6 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -125,7 +125,7 @@ template bool containsStateUpdates(const T &e) { - return std::visit([](const auto &ev) { return Cache::isStateEvent(ev); }, e); + return std::visit([](const auto &ev) { return Cache::isStateEvent_; }, e); } bool diff --git a/src/Cache_p.h b/src/Cache_p.h index 5d700658..30c365a6 100644 --- a/src/Cache_p.h +++ b/src/Cache_p.h @@ -291,15 +291,9 @@ public: std::optional secret(const std::string name); template - static constexpr bool isStateEvent(const mtx::events::StateEvent &) - { - return true; - } - template - static constexpr bool isStateEvent(const mtx::events::Event &) - { - return false; - } + constexpr static bool isStateEvent_ = + std::is_same_v>, + mtx::events::StateEvent().content)>>; static int compare_state_key(const MDB_val *a, const MDB_val *b) { @@ -416,11 +410,27 @@ private: } std::visit( - [&txn, &statesdb, &stateskeydb, &eventsDb](auto e) { - if constexpr (isStateEvent(e)) { + [&txn, &statesdb, &stateskeydb, &eventsDb, &membersdb](const auto &e) { + if constexpr (isStateEvent_) { eventsDb.put(txn, e.event_id, json(e).dump()); - if (e.type != EventType::Unsupported) { + if (std::is_same_v< + std::remove_cv_t>, + StateEvent>) { + if (e.type == EventType::RoomMember) + membersdb.del(txn, e.state_key, ""); + else if (e.state_key.empty()) + statesdb.del(txn, to_string(e.type)); + else + stateskeydb.del( + txn, + to_string(e.type), + json::object({ + {"key", e.state_key}, + {"id", e.event_id}, + }) + .dump()); + } else if (e.type != EventType::Unsupported) { if (e.state_key.empty()) statesdb.put( txn, to_string(e.type), json(e).dump()); diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index ab0f8152..b8648269 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -22,7 +22,14 @@ QHash infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - MxcImageResponse *response = new MxcImageResponse(id, requestedSize); + auto id_ = id; + bool crop = true; + if (id.endsWith("?scale")) { + crop = false; + id_.remove("?scale"); + } + + MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize); pool.start(response); return response; } @@ -36,20 +43,24 @@ void MxcImageResponse::run() { MxcImageProvider::download( - m_id, m_requestedSize, [this](QString, QSize, QImage image, QString) { + m_id, + m_requestedSize, + [this](QString, QSize, QImage image, QString) { if (image.isNull()) { m_error = "Failed to download image."; } else { m_image = image; } emit finished(); - }); + }, + m_crop); } void MxcImageProvider::download(const QString &id, const QSize &requestedSize, - std::function then) + std::function then, + bool crop) { std::optional encryptionInfo; auto temp = infos.find("mxc://" + id); @@ -58,11 +69,12 @@ MxcImageProvider::download(const QString &id, if (requestedSize.isValid() && !encryptionInfo) { QString fileName = - QString("%1_%2x%3_crop") + QString("%1_%2x%3_%4") .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) .arg(requestedSize.width()) - .arg(requestedSize.height()); + .arg(requestedSize.height()) + .arg(crop ? "crop" : "scale"); QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); @@ -85,7 +97,7 @@ MxcImageProvider::download(const QString &id, opts.mxc_url = "mxc://" + id.toStdString(); opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1; opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1; - opts.method = "crop"; + opts.method = crop ? "crop" : "scale"; http::client()->get_thumbnail( opts, [fileInfo, requestedSize, then, id](const std::string &res, diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 7b960836..61d82852 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,9 +19,10 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, const QSize &requestedSize) + MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize) : m_id(id) , m_requestedSize(requestedSize) + , m_crop(crop) { setAutoDelete(false); } @@ -37,6 +38,7 @@ public: QString m_id, m_error; QSize m_requestedSize; QImage m_image; + bool m_crop; }; class MxcImageProvider @@ -51,7 +53,8 @@ public slots: static void addEncryptionInfo(mtx::crypto::EncryptedFile info); static void download(const QString &id, const QSize &requestedSize, - std::function then); + std::function then, + bool crop = true); private: QThreadPool pool; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index 6c508da0..d3cc8014 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -5,12 +5,18 @@ #include "SingleImagePackModel.h" #include "Cache_p.h" +#include "ChatPage.h" #include "MatrixClient.h" +#include "timeline/Permissions.h" +#include "timeline/TimelineModel.h" + +#include "Logging.h" SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) , roomid_(std::move(pack_.source_room)) , statekey_(std::move(pack_.state_key)) + , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { if (!pack.pack) @@ -61,6 +67,73 @@ SingleImagePackModel::data(const QModelIndex &index, int role) const return {}; } +bool +SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + using mtx::events::msc2545::PackUsage; + + if (hasIndex(index.row(), index.column(), index.parent())) { + auto &img = pack.images.at(shortcodes.at(index.row())); + switch (role) { + case ShortCode: { + auto newCode = value.toString().toStdString(); + + // otherwise we delete this by accident + if (pack.images.count(newCode)) + return false; + + auto tmp = img; + auto oldCode = shortcodes.at(index.row()); + pack.images.erase(oldCode); + shortcodes[index.row()] = newCode; + pack.images.insert({newCode, tmp}); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::ShortCode}); + return true; + } + case Body: + img.body = value.toString().toStdString(); + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::Body}); + return true; + case IsEmote: { + bool isEmote = value.toBool(); + bool isSticker = + img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsEmote}); + + return true; + } + case IsSticker: { + bool isEmote = + img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji(); + bool isSticker = value.toBool(); + + img.usage.set(PackUsage::Emoji, isEmote); + img.usage.set(PackUsage::Sticker, isSticker); + + if (img.usage == pack.pack->usage) + img.usage.reset(); + + emit dataChanged( + this->index(index.row()), this->index(index.row()), {Roles::IsSticker}); + + return true; + } + } + } + return false; +} + bool SingleImagePackModel::isGloballyEnabled() const { @@ -98,3 +171,111 @@ SingleImagePackModel::setGloballyEnabled(bool enabled) // emit this->globallyEnabledChanged(); }); } + +bool +SingleImagePackModel::canEdit() const +{ + if (roomid_.empty()) + return true; + else + return Permissions(QString::fromStdString(roomid_)) + .canChange(qml_mtx_events::ImagePackInRoom); +} + +void +SingleImagePackModel::setPackname(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->display_name) { + this->pack.pack->display_name = val_; + emit packnameChanged(); + } +} + +void +SingleImagePackModel::setAttribution(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->attribution) { + this->pack.pack->attribution = val_; + emit attributionChanged(); + } +} + +void +SingleImagePackModel::setAvatarUrl(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != this->pack.pack->avatar_url) { + this->pack.pack->avatar_url = val_; + emit avatarUrlChanged(); + } +} + +void +SingleImagePackModel::setStatekey(QString val) +{ + auto val_ = val.toStdString(); + if (val_ != statekey_) { + statekey_ = val_; + emit statekeyChanged(); + } +} + +void +SingleImagePackModel::setIsStickerPack(bool val) +{ + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_sticker()) { + pack.pack->usage.set(PackUsage::Sticker, val); + emit isStickerPackChanged(); + } +} + +void +SingleImagePackModel::setIsEmotePack(bool val) +{ + using mtx::events::msc2545::PackUsage; + if (val != pack.pack->is_emoji()) { + pack.pack->usage.set(PackUsage::Emoji, val); + emit isEmotePackChanged(); + } +} + +void +SingleImagePackModel::save() +{ + if (roomid_.empty()) { + http::client()->put_account_data(pack, [this](mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } else { + if (old_statekey_ != statekey_) { + http::client()->send_state_event( + roomid_, + to_string(mtx::events::EventType::ImagePackInRoom), + old_statekey_, + nlohmann::json::object(), + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to delete old image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } + + http::client()->send_state_event( + roomid_, + statekey_, + pack, + [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + if (e) + ChatPage::instance()->showNotification( + tr("Failed to update image pack: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + }); + } +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index e0c791ba..44f413c6 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -15,14 +15,18 @@ class SingleImagePackModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(QString roomid READ roomid CONSTANT) - Q_PROPERTY(QString statekey READ statekey CONSTANT) - Q_PROPERTY(QString attribution READ statekey CONSTANT) - Q_PROPERTY(QString packname READ packname CONSTANT) - Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) - Q_PROPERTY(bool isStickerPack READ isStickerPack CONSTANT) - Q_PROPERTY(bool isEmotePack READ isEmotePack CONSTANT) + Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged) + Q_PROPERTY( + QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged) + Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged) + Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged) + Q_PROPERTY( + bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged) + Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged) Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY globallyEnabledChanged) + Q_PROPERTY(bool canEdit READ canEdit CONSTANT) + public: enum Roles { @@ -32,11 +36,15 @@ public: IsEmote, IsSticker, }; + Q_ENUM(Roles); SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; QString roomid() const { return QString::fromStdString(roomid_); } QString statekey() const { return QString::fromStdString(statekey_); } @@ -47,14 +55,30 @@ public: bool isEmotePack() const { return pack.pack->is_emoji(); } bool isGloballyEnabled() const; + bool canEdit() const; void setGloballyEnabled(bool enabled); + void setPackname(QString val); + void setAttribution(QString val); + void setAvatarUrl(QString val); + void setStatekey(QString val); + void setIsStickerPack(bool val); + void setIsEmotePack(bool val); + + Q_INVOKABLE void save(); + signals: void globallyEnabledChanged(); + void statekeyChanged(); + void attributionChanged(); + void packnameChanged(); + void avatarUrlChanged(); + void isEmotePackChanged(); + void isStickerPackChanged(); private: std::string roomid_; - std::string statekey_; + std::string statekey_, old_statekey_; mtx::events::msc2545::ImagePack pack; std::vector shortcodes; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index a8adf05b..10d9788d 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -308,6 +308,15 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t) case qml_mtx_events::KeyVerificationDone: case qml_mtx_events::KeyVerificationReady: return mtx::events::EventType::RoomMessage; + //! m.image_pack, currently im.ponies.room_emotes + case qml_mtx_events::ImagePackInRoom: + return mtx::events::EventType::ImagePackRooms; + //! m.image_pack, currently im.ponies.user_emotes + case qml_mtx_events::ImagePackInAccountData: + return mtx::events::EventType::ImagePackInAccountData; + //! m.image_pack.rooms, currently im.ponies.emote_rooms + case qml_mtx_events::ImagePackRooms: + return mtx::events::EventType::ImagePackRooms; default: return mtx::events::EventType::Unsupported; }; diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index f62c5360..b5c8ca37 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -107,7 +107,13 @@ enum EventType KeyVerificationCancel, KeyVerificationKey, KeyVerificationDone, - KeyVerificationReady + KeyVerificationReady, + //! m.image_pack, currently im.ponies.room_emotes + ImagePackInRoom, + //! m.image_pack, currently im.ponies.user_emotes + ImagePackInAccountData, + //! m.image_pack.rooms, currently im.ponies.emote_rooms + ImagePackRooms, }; Q_ENUM_NS(EventType) mtx::events::EventType fromRoomEventType(qml_mtx_events::EventType); -- cgit 1.5.1 From 16d0190f4e1ee79025ec47f3dcfa1fb701a63ff1 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 03:28:56 +0200 Subject: Allow uploading additional stickers --- resources/qml/ScrollHelper.qml | 7 ++- resources/qml/dialogs/ImagePackEditorDialog.qml | 18 +++++++ src/SingleImagePackModel.cpp | 69 ++++++++++++++++++++++++- src/SingleImagePackModel.h | 8 +++ 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/resources/qml/ScrollHelper.qml b/resources/qml/ScrollHelper.qml index e584ae3d..e84e67fd 100644 --- a/resources/qml/ScrollHelper.qml +++ b/resources/qml/ScrollHelper.qml @@ -23,6 +23,9 @@ MouseArea { // console.warn("Delta: ", wheel.pixelDelta.y); // console.warn("Old position: ", flickable.contentY); // console.warn("New position: ", newPos); + // breaks ListView's with headers... + //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) + // minYExtent += flickableItem.headerItem.height; id: root @@ -30,10 +33,6 @@ MouseArea { property alias enabled: root.enabled function calculateNewPosition(flickableItem, wheel) { - // breaks ListView's with headers... - //if (typeof (flickableItem.headerItem) !== "undefined" && flickableItem.headerItem) - // minYExtent += flickableItem.headerItem.height; - //Nothing to scroll if (flickableItem.contentHeight < flickableItem.height) return flickableItem.contentY; diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 0049d3b4..89301215 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -4,6 +4,7 @@ import ".." import "../components" +import Qt.labs.platform 1.1 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 @@ -78,6 +79,23 @@ ApplicationWindow { } + footer: Button { + palette: Nheko.colors + onClicked: addFilesDialog.open() + width: ListView.view.width + text: qsTr("Add images") + + FileDialog { + id: addFilesDialog + + folder: StandardPaths.writableLocation(StandardPaths.PicturesLocation) + fileMode: FileDialog.OpenFiles + nameFilters: [qsTr("Stickers (*.png *.webp)")] + onAccepted: imagePack.addStickers(files) + } + + } + delegate: AvatarListTile { id: packItem diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index d3cc8014..ddecf1ad 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -4,13 +4,18 @@ #include "SingleImagePackModel.h" +#include +#include + #include "Cache_p.h" #include "ChatPage.h" +#include "Logging.h" #include "MatrixClient.h" +#include "Utils.h" #include "timeline/Permissions.h" #include "timeline/TimelineModel.h" -#include "Logging.h" +Q_DECLARE_METATYPE(mtx::common::ImageInfo); SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) @@ -19,11 +24,15 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) , old_statekey_(statekey_) , pack(std::move(pack_.pack)) { + [[maybe_unused]] static auto imageInfoType = qRegisterMetaType(); + if (!pack.pack) pack.pack = mtx::events::msc2545::ImagePack::PackDescription{}; for (const auto &e : pack.images) shortcodes.push_back(e.first); + + connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb); } int @@ -279,3 +288,61 @@ SingleImagePackModel::save() }); } } + +void +SingleImagePackModel::addStickers(QList files) +{ + for (const auto &f : files) { + auto file = QFile(f.toLocalFile()); + if (!file.open(QFile::ReadOnly)) { + ChatPage::instance()->showNotification( + tr("Failed to open image: {}").arg(f.toLocalFile())); + return; + } + + auto bytes = file.readAll(); + auto img = utils::readImage(bytes); + + mtx::common::ImageInfo info{}; + + auto sz = img.size() / 2; + if (sz.width() > 512 || sz.height() > 512) { + sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio); + } + + info.h = sz.height(); + info.w = sz.width(); + info.size = bytes.size(); + + auto filename = f.fileName().toStdString(); + http::client()->upload( + bytes.toStdString(), + QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(), + filename, + [this, filename, info](const mtx::responses::ContentURI &uri, + mtx::http::RequestErr e) { + if (e) { + ChatPage::instance()->showNotification( + tr("Failed to upload image: {}") + .arg(QString::fromStdString(e->matrix_error.error))); + return; + } + + emit addImage(uri.content_uri, filename, info); + }); + } +} +void +SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info) +{ + mtx::events::msc2545::PackImage img{}; + img.url = uri; + img.info = info; + beginInsertRows( + QModelIndex(), static_cast(shortcodes.size()), static_cast(shortcodes.size())); + + pack.images[filename] = img; + shortcodes.push_back(filename); + + endInsertRows(); +} diff --git a/src/SingleImagePackModel.h b/src/SingleImagePackModel.h index 44f413c6..cd38b3b6 100644 --- a/src/SingleImagePackModel.h +++ b/src/SingleImagePackModel.h @@ -5,6 +5,8 @@ #pragma once #include +#include +#include #include @@ -66,6 +68,7 @@ public: void setIsEmotePack(bool val); Q_INVOKABLE void save(); + Q_INVOKABLE void addStickers(QList files); signals: void globallyEnabledChanged(); @@ -76,6 +79,11 @@ signals: void isEmotePackChanged(); void isStickerPackChanged(); + void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info); + +private slots: + void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info); + private: std::string roomid_; std::string statekey_, old_statekey_; -- cgit 1.5.1 From e5a6b2b6efcb56072146aad5996132070f9c2078 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 04:31:30 +0200 Subject: Allow creating new packs --- resources/qml/dialogs/ImagePackEditorDialog.qml | 8 +++---- resources/qml/dialogs/ImagePackSettingsDialog.qml | 28 +++++++++++++++++++++++ src/Cache.cpp | 2 +- src/ImagePackListModel.cpp | 18 +++++++++++++++ src/ImagePackListModel.h | 4 ++++ src/SingleImagePackModel.cpp | 4 +++- 6 files changed, 58 insertions(+), 6 deletions(-) diff --git a/resources/qml/dialogs/ImagePackEditorDialog.qml b/resources/qml/dialogs/ImagePackEditorDialog.qml index 89301215..b839c9e3 100644 --- a/resources/qml/dialogs/ImagePackEditorDialog.qml +++ b/resources/qml/dialogs/ImagePackEditorDialog.qml @@ -186,7 +186,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.isEmotePack - onToggled: imagePack.isEmotePack = checked + onClicked: imagePack.isEmotePack = checked Layout.alignment: Qt.AlignRight } @@ -196,7 +196,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.isStickerPack - onToggled: imagePack.isStickerPack = checked + onClicked: imagePack.isStickerPack = checked Layout.alignment: Qt.AlignRight } @@ -251,7 +251,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsEmote) - onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsEmote) + onClicked: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsEmote) Layout.alignment: Qt.AlignRight } @@ -261,7 +261,7 @@ ApplicationWindow { ToggleButton { checked: imagePack.data(imagePack.index(currentImageIndex, 0), SingleImagePackModel.IsSticker) - onToggled: imagePack.setData(imagePack.index(currentImageIndex, 0), text, SingleImagePackModel.IsSticker) + onClicked: imagePack.setData(imagePack.index(currentImageIndex, 0), checked, SingleImagePackModel.IsSticker) Layout.alignment: Qt.AlignRight } diff --git a/resources/qml/dialogs/ImagePackSettingsDialog.qml b/resources/qml/dialogs/ImagePackSettingsDialog.qml index c57867fd..5181619c 100644 --- a/resources/qml/dialogs/ImagePackSettingsDialog.qml +++ b/resources/qml/dialogs/ImagePackSettingsDialog.qml @@ -62,6 +62,34 @@ ApplicationWindow { enabled: !Settings.mobileMode } + footer: ColumnLayout { + Button { + palette: Nheko.colors + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(false) + }); + dialog.show(); + } + width: packlist.width + visible: !packlist.containsAccountPack + text: qsTr("Create account pack") + } + + Button { + palette: Nheko.colors + onClicked: { + var dialog = packEditor.createObject(timelineRoot, { + "imagePack": packlist.newPack(true) + }); + dialog.show(); + } + width: packlist.width + text: qsTr("New room pack") + } + + } + delegate: AvatarListTile { id: packItem diff --git a/src/Cache.cpp b/src/Cache.cpp index f3f3dbb6..6650334a 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -3401,7 +3401,7 @@ Cache::getImagePacks(const std::string &room_id, std::optional stickers) info.pack.pack = pack.pack; for (const auto &img : pack.images) { - if (img.second.overrides_usage() && + if (stickers.has_value() && img.second.overrides_usage() && (stickers ? !img.second.is_sticker() : !img.second.is_emoji())) continue; diff --git a/src/ImagePackListModel.cpp b/src/ImagePackListModel.cpp index 89f1f68e..6392de22 100644 --- a/src/ImagePackListModel.cpp +++ b/src/ImagePackListModel.cpp @@ -74,3 +74,21 @@ ImagePackListModel::packAt(int row) QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership); return e; } + +SingleImagePackModel * +ImagePackListModel::newPack(bool inRoom) +{ + ImagePackInfo info{}; + if (inRoom) + info.source_room = room_id; + return new SingleImagePackModel(info); +} + +bool +ImagePackListModel::containsAccountPack() const +{ + for (const auto &p : packs) + if (p->roomid().isEmpty()) + return true; + return false; +} diff --git a/src/ImagePackListModel.h b/src/ImagePackListModel.h index 0a044690..2aa5abb2 100644 --- a/src/ImagePackListModel.h +++ b/src/ImagePackListModel.h @@ -12,6 +12,7 @@ class SingleImagePackModel; class ImagePackListModel : public QAbstractListModel { Q_OBJECT + Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT) public: enum Roles { @@ -29,6 +30,9 @@ public: QVariant data(const QModelIndex &index, int role) const override; Q_INVOKABLE SingleImagePackModel *packAt(int row); + Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom); + + bool containsAccountPack() const; private: std::string room_id; diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index ddecf1ad..dea25264 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -15,7 +15,7 @@ #include "timeline/Permissions.h" #include "timeline/TimelineModel.h" -Q_DECLARE_METATYPE(mtx::common::ImageInfo); +Q_DECLARE_METATYPE(mtx::common::ImageInfo) SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent) : QAbstractListModel(parent) @@ -285,6 +285,8 @@ SingleImagePackModel::save() ChatPage::instance()->showNotification( tr("Failed to update image pack: {}") .arg(QString::fromStdString(e->matrix_error.error))); + + nhlog::net()->info("Uploaded image pack: {}", statekey_); }); } } -- cgit 1.5.1 From cc22309c5b549d068e4cb1f4da91d346bb76562f Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 6 Aug 2021 04:43:56 +0200 Subject: this is not needed for translations --- src/SingleImagePackModel.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SingleImagePackModel.cpp b/src/SingleImagePackModel.cpp index dea25264..7bf55617 100644 --- a/src/SingleImagePackModel.cpp +++ b/src/SingleImagePackModel.cpp @@ -255,7 +255,7 @@ void SingleImagePackModel::save() { if (roomid_.empty()) { - http::client()->put_account_data(pack, [this](mtx::http::RequestErr e) { + http::client()->put_account_data(pack, [](mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( tr("Failed to update image pack: {}") @@ -268,7 +268,7 @@ SingleImagePackModel::save() to_string(mtx::events::EventType::ImagePackInRoom), old_statekey_, nlohmann::json::object(), - [this](const mtx::responses::EventId &, mtx::http::RequestErr e) { + [](const mtx::responses::EventId &, mtx::http::RequestErr e) { if (e) ChatPage::instance()->showNotification( tr("Failed to delete old image pack: {}") -- cgit 1.5.1 From 001f87fe7726be5a3cdf9a1c145f361740d90eae Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 01:00:18 +0200 Subject: Fix redactions --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8bc855d..55b58da1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG e5688a2c5987a614b5055595f991f18568127bd2 + GIT_TAG bcf363cb5e6c423f40c96123e227bc8c5f6d6f80 ) 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 2c0c5ebf..a363aeff 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -161,7 +161,7 @@ modules: buildsystem: cmake-ninja name: mtxclient sources: - - commit: e5688a2c5987a614b5055595f991f18568127bd2 + - commit: bcf363cb5e6c423f40c96123e227bc8c5f6d6f80 type: git url: https://github.com/Nheko-Reborn/mtxclient.git - config-opts: -- cgit 1.5.1 From 7a7ba47c013c0a229e8a4ac2e11107d4e4069948 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 01:24:13 +0200 Subject: Share shm in flatpak fixes #562 Requires flatpak 1.11.1 --- io.github.NhekoReborn.Nheko.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index a363aeff..638f278a 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -8,6 +8,7 @@ rename-desktop-file: nheko.desktop rename-appdata-file: nheko.appdata.xml finish-args: - --device=dri + - --device=shm # needed for webcams, see #517 - --device=all - --share=ipc @@ -19,6 +20,8 @@ finish-args: - --talk-name=org.freedesktop.secrets - --talk-name=org.freedesktop.StatusNotifierItem - --talk-name=org.kde.* + # needed for SingleApplication to work + - --allow=per-app-dev-shm cleanup: - /include - /bin/mdb* -- cgit 1.5.1 From 3e53b8cc09f63b1533f505b398ec7dfe4e8b3a43 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 01:38:46 +0200 Subject: Install recent flatpak in CI --- .gitlab-ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e24c5c85..cea6be7b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -91,7 +91,9 @@ build-flatpak-amd64: #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' tags: [docker] before_script: - - apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 + # need flatpak 1.11.1 at least + - apt-get update && apt-get install -y software-properties-common + - add-apt-repository ppa:alexlarsson/flatpak && apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 @@ -119,7 +121,9 @@ build-flatpak-arm64: #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' tags: [docker-arm64] before_script: - - apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 + # need flatpak 1.11.1 at least + - apt-get update && apt-get install -y software-properties-common + - add-apt-repository ppa:alexlarsson/flatpak && apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 -- cgit 1.5.1 From 483769a2a1b0e158c0ef7f332486ec644332e20b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 01:48:40 +0200 Subject: device=shm not needed in flatpak --- io.github.NhekoReborn.Nheko.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml index 638f278a..a0e57b09 100644 --- a/io.github.NhekoReborn.Nheko.yaml +++ b/io.github.NhekoReborn.Nheko.yaml @@ -8,7 +8,6 @@ rename-desktop-file: nheko.desktop rename-appdata-file: nheko.appdata.xml finish-args: - --device=dri - - --device=shm # needed for webcams, see #517 - --device=all - --share=ipc -- cgit 1.5.1 From 72bbad7485db6ac1803f81344c29b93d9fa70945 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 22:51:09 +0200 Subject: Show encryption errors in qml and add request keys button --- CMakeLists.txt | 13 +- resources/qml/MessageView.qml | 2 + resources/qml/TimelineRow.qml | 3 + resources/qml/delegates/Encrypted.qml | 48 ++++++ resources/qml/delegates/MessageDelegate.qml | 11 ++ resources/qml/delegates/Reply.qml | 2 + resources/res.qrc | 9 +- src/Olm.cpp | 2 +- src/Olm.h | 9 +- src/timeline/EventStore.cpp | 256 ++++++++++++---------------- src/timeline/EventStore.h | 9 +- src/timeline/TimelineModel.cpp | 16 ++ src/timeline/TimelineModel.h | 3 + src/timeline/TimelineViewManager.cpp | 2 + 14 files changed, 220 insertions(+), 165 deletions(-) create mode 100644 resources/qml/delegates/Encrypted.qml diff --git a/CMakeLists.txt b/CMakeLists.txt index 55b58da1..049ed8a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -541,32 +541,33 @@ qt5_wrap_cpp(MOC_HEADERS src/AvatarProvider.h src/BlurhashProvider.h - src/Cache_p.h src/CacheCryptoStructs.h + src/Cache_p.h src/CallDevices.h src/CallManager.h src/ChatPage.h src/Clipboard.h + src/CombinedImagePackModel.h src/CompletionProxyModel.h src/DeviceVerificationFlow.h + src/ImagePackListModel.h src/InviteesModel.h src/LoginPage.h src/MainWindow.h src/MemberList.h src/MxcImageProvider.h - src/ReadReceiptsModel.h + src/Olm.h src/RegisterPage.h + src/RoomsModel.h src/SSOHandler.h - src/CombinedImagePackModel.h src/SingleImagePackModel.h - src/ImagePackListModel.h src/TrayIcon.h src/UserSettingsPage.h src/UsersModel.h - src/RoomsModel.h src/WebRTCSession.h src/WelcomePage.h - ) + src/ReadReceiptsModel.h +) # # Bundle translations. diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index f3e15d84..79cbd700 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -349,6 +349,7 @@ ScrollView { required property string callType required property var reactions required property int trustlevel + required property int encryptionError required property var timestamp required property int status required property int index @@ -456,6 +457,7 @@ ScrollView { callType: wrapper.callType reactions: wrapper.reactions trustlevel: wrapper.trustlevel + encryptionError: wrapper.encryptionError timestamp: wrapper.timestamp status: wrapper.status relatedEventCacheBuster: wrapper.relatedEventCacheBuster diff --git a/resources/qml/TimelineRow.qml b/resources/qml/TimelineRow.qml index 6345f44c..c612479a 100644 --- a/resources/qml/TimelineRow.qml +++ b/resources/qml/TimelineRow.qml @@ -38,6 +38,7 @@ Item { required property string callType required property var reactions required property int trustlevel + required property int encryptionError required property var timestamp required property int status required property int relatedEventCacheBuster @@ -110,6 +111,7 @@ Item { roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? "" roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? "" callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? "" + encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? "" relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0 } @@ -136,6 +138,7 @@ Item { roomTopic: r.roomTopic roomName: r.roomName callType: r.callType + encryptionError: r.encryptionError relatedEventCacheBuster: r.relatedEventCacheBuster isReply: false } diff --git a/resources/qml/delegates/Encrypted.qml b/resources/qml/delegates/Encrypted.qml new file mode 100644 index 00000000..cd00a9d4 --- /dev/null +++ b/resources/qml/delegates/Encrypted.qml @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2021 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import ".." +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.2 +import im.nheko 1.0 + +ColumnLayout { + id: r + + required property int encryptionError + required property string eventId + + width: parent ? parent.width : undefined + + MatrixText { + text: { + switch (encryptionError) { + case Olm.MissingSession: + return qsTr("There is no key to unlock this message. We requested the key automatically, but you can try requesting it again if you are impatient."); + case Olm.MissingSessionIndex: + return qsTr("This message couldn't be decrypted, because we only have a key for newer messages. You can try requesting access to this message."); + case Olm.DbError: + return qsTr("There was an internal error reading the decryption key from the database."); + case Olm.DecryptionFailed: + return qsTr("There was an error decrypting this message."); + case Olm.ParsingFailed: + return qsTr("The message couldn't be parsed."); + case Olm.ReplayAttack: + return qsTr("The encryption key was reused! Someone is possibly trying to insert false messages into this chat!"); + default: + return qsTr("Unknown decryption error"); + } + } + color: Nheko.colors.buttonText + width: r ? r.width : undefined + } + + Button { + palette: Nheko.colors + visible: encryptionError == Olm.MissingSession || encryptionError == Olm.MissingSessionIndex + text: qsTr("Request key") + onClicked: room.requestKeyForEvent(eventId) + } + +} diff --git a/resources/qml/delegates/MessageDelegate.qml b/resources/qml/delegates/MessageDelegate.qml index a98c2a8b..a8bdf183 100644 --- a/resources/qml/delegates/MessageDelegate.qml +++ b/resources/qml/delegates/MessageDelegate.qml @@ -29,6 +29,7 @@ Item { required property string roomTopic required property string roomName required property string callType + required property int encryptionError required property int relatedEventCacheBuster height: chooser.childrenRect.height @@ -189,6 +190,16 @@ Item { } + DelegateChoice { + roleValue: MtxEvent.Encrypted + + Encrypted { + encryptionError: d.encryptionError + eventId: d.eventId + } + + } + DelegateChoice { roleValue: MtxEvent.Name diff --git a/resources/qml/delegates/Reply.qml b/resources/qml/delegates/Reply.qml index 3e02a940..8bbce10e 100644 --- a/resources/qml/delegates/Reply.qml +++ b/resources/qml/delegates/Reply.qml @@ -30,6 +30,7 @@ Item { property string roomTopic property string roomName property string callType + property int encryptionError property int relatedEventCacheBuster width: parent.width @@ -97,6 +98,7 @@ Item { roomName: r.roomName callType: r.callType relatedEventCacheBuster: r.relatedEventCacheBuster + encryptionError: r.encryptionError enabled: false width: parent.width isReply: true diff --git a/resources/res.qrc b/resources/res.qrc index d7187f42..f50265ca 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -143,14 +143,15 @@ qml/emoji/StickerPicker.qml qml/UserProfile.qml qml/delegates/MessageDelegate.qml - qml/delegates/TextMessage.qml - qml/delegates/NoticeMessage.qml - qml/delegates/ImageMessage.qml - qml/delegates/PlayableMediaMessage.qml + qml/delegates/Encrypted.qml qml/delegates/FileMessage.qml + qml/delegates/ImageMessage.qml + qml/delegates/NoticeMessage.qml qml/delegates/Pill.qml qml/delegates/Placeholder.qml + qml/delegates/PlayableMediaMessage.qml qml/delegates/Reply.qml + qml/delegates/TextMessage.qml qml/device-verification/Waiting.qml qml/device-verification/DeviceVerification.qml qml/device-verification/DigitVerification.qml diff --git a/src/Olm.cpp b/src/Olm.cpp index 048a6c0f..293b12de 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1069,7 +1069,7 @@ decryptEvent(const MegolmSessionIndex &index, mtx::events::collections::TimelineEvent te; mtx::events::collections::from_json(body, te); - return {std::nullopt, std::nullopt, std::move(te.data)}; + return {DecryptionErrorCode::NoError, std::nullopt, std::move(te.data)}; } catch (std::exception &e) { return {DecryptionErrorCode::ParsingFailed, e.what(), std::nullopt}; } diff --git a/src/Olm.h b/src/Olm.h index a18cbbfb..ac1a1617 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -14,9 +14,11 @@ constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2"; namespace olm { +Q_NAMESPACE -enum class DecryptionErrorCode +enum DecryptionErrorCode { + NoError, MissingSession, // Session was not found, retrieve from backup or request from other devices // and try again MissingSessionIndex, // Session was found, but it does not reach back enough to this index, @@ -25,14 +27,13 @@ enum class DecryptionErrorCode DecryptionFailed, // libolm error ParsingFailed, // Failed to parse the actual event ReplayAttack, // Megolm index reused - UnknownFingerprint, // Unknown device Fingerprint }; +Q_ENUM_NS(DecryptionErrorCode) struct DecryptionResult { - std::optional error; + DecryptionErrorCode error; std::optional error_message; - std::optional event; }; diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp index 9a91ff79..742f8dbb 100644 --- a/src/timeline/EventStore.cpp +++ b/src/timeline/EventStore.cpp @@ -20,8 +20,7 @@ Q_DECLARE_METATYPE(Reaction) -QCache EventStore::decryptedEvents_{ - 1000}; +QCache EventStore::decryptedEvents_{1000}; QCache EventStore::events_by_id_{ 1000}; QCache EventStore::events_{1000}; @@ -144,12 +143,16 @@ EventStore::EventStore(std::string room_id, QObject *) mtx::events::msg::Encrypted>) { auto event = decryptEvent({room_id_, e.event_id}, e); - if (auto dec = - std::get_if>(event)) { - emit updateFlowEventId( - event_id.event_id.to_string()); + if (event->event) { + if (auto dec = std::get_if< + mtx::events::RoomEvent< + mtx::events::msg:: + KeyVerificationRequest>>( + &event->event.value())) { + emit updateFlowEventId( + event_id.event_id + .to_string()); + } } } }); @@ -393,12 +396,12 @@ EventStore::handleSync(const mtx::responses::Timeline &events) if (auto encrypted = std::get_if>( &event)) { - mtx::events::collections::TimelineEvents *d_event = - decryptEvent({room_id_, encrypted->event_id}, *encrypted); - if (std::visit( + auto d_event = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + if (d_event->event && + std::visit( [](auto e) { return (e.sender != utils::localUser().toStdString()); }, - *d_event)) { - handle_room_verification(*d_event); + *d_event->event)) { + handle_room_verification(*d_event->event); } } } @@ -599,11 +602,15 @@ EventStore::get(int idx, bool decrypt) events_.insert(index, event_ptr); } - if (decrypt) + if (decrypt) { if (auto encrypted = std::get_if>( - event_ptr)) - return decryptEvent({room_id_, encrypted->event_id}, *encrypted); + event_ptr)) { + auto decrypted = decryptEvent({room_id_, encrypted->event_id}, *encrypted); + if (decrypted->event) + return &*decrypted->event; + } + } return event_ptr; } @@ -629,7 +636,7 @@ EventStore::indexToId(int idx) const return cache::client()->getTimelineEventId(room_id_, toInternalIdx(idx)); } -mtx::events::collections::TimelineEvents * +olm::DecryptionResult * EventStore::decryptEvent(const IdIndex &idx, const mtx::events::EncryptedEvent &e) { @@ -641,57 +648,24 @@ EventStore::decryptEvent(const IdIndex &idx, index.session_id = e.content.session_id; index.sender_key = e.content.sender_key; - auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) { - auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event)); + auto asCacheEntry = [&idx](olm::DecryptionResult &&event) { + auto event_ptr = new olm::DecryptionResult(std::move(event)); decryptedEvents_.insert(idx, event_ptr); return event_ptr; }; auto decryptionResult = olm::decryptEvent(index, e); - mtx::events::RoomEvent dummy; - dummy.origin_server_ts = e.origin_server_ts; - dummy.event_id = e.event_id; - dummy.sender = e.sender; - if (decryptionResult.error) { - switch (*decryptionResult.error) { + switch (decryptionResult.error) { case olm::DecryptionErrorCode::MissingSession: case olm::DecryptionErrorCode::MissingSessionIndex: { - if (decryptionResult.error == olm::DecryptionErrorCode::MissingSession) - dummy.content.body = - tr("-- Encrypted Event (No keys found for decryption) --", - "Placeholder, when the message was not decrypted yet or can't " - "be " - "decrypted.") - .toStdString(); - else - dummy.content.body = - tr("-- Encrypted Event (Key not valid for this index) --", - "Placeholder, when the message can't be decrypted with this " - "key since it is not valid for this index ") - .toStdString(); nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})", index.room_id, index.session_id, e.sender); - // we may not want to request keys during initial sync and such - if (suppressKeyRequests) - break; - // TODO: Check if this actually works and look in key backup - auto copy = e; - copy.room_id = room_id_; - if (pending_key_requests.count(e.content.session_id)) { - pending_key_requests.at(e.content.session_id) - .events.push_back(copy); - } else { - PendingKeyRequests request; - request.request_id = - "key_request." + http::client()->generate_txn_id(); - request.events.push_back(copy); - olm::send_key_request_for(copy, request.request_id); - pending_key_requests[e.content.session_id] = request; - } + + requestSession(e, false); break; } case olm::DecryptionErrorCode::DbError: @@ -701,12 +675,6 @@ EventStore::decryptEvent(const IdIndex &idx, index.session_id, index.sender_key, decryptionResult.error_message.value_or("")); - dummy.content.body = - tr("-- Decryption Error (failed to retrieve megolm keys from db) --", - "Placeholder, when the message can't be decrypted, because the DB " - "access " - "failed.") - .toStdString(); break; case olm::DecryptionErrorCode::DecryptionFailed: nhlog::crypto()->critical( @@ -715,22 +683,8 @@ EventStore::decryptEvent(const IdIndex &idx, index.session_id, index.sender_key, decryptionResult.error_message.value_or("")); - dummy.content.body = - tr("-- Decryption Error (%1) --", - "Placeholder, when the message can't be decrypted. In this case, the " - "Olm " - "decrytion returned an error, which is passed as %1.") - .arg( - QString::fromStdString(decryptionResult.error_message.value_or(""))) - .toStdString(); break; case olm::DecryptionErrorCode::ParsingFailed: - dummy.content.body = - tr("-- Encrypted Event (Unknown event type) --", - "Placeholder, when the message was decrypted, but we couldn't parse " - "it, because " - "Nheko/mtxclient don't support that event type yet.") - .toStdString(); break; case olm::DecryptionErrorCode::ReplayAttack: nhlog::crypto()->critical( @@ -738,85 +692,50 @@ EventStore::decryptEvent(const IdIndex &idx, e.event_id, room_id_, index.sender_key); - dummy.content.body = - tr("-- Replay attack! This message index was reused! --").toStdString(); break; - case olm::DecryptionErrorCode::UnknownFingerprint: - // TODO: don't fail, just show in UI. - nhlog::crypto()->critical("Message by unverified fingerprint {}", - index.sender_key); - dummy.content.body = - tr("-- Message by unverified device! --").toStdString(); + case olm::DecryptionErrorCode::NoError: + // unreachable break; } - return asCacheEntry(std::move(dummy)); - } - - std::string msg_str; - try { - auto session = cache::client()->getInboundMegolmSession(index); - auto res = - olm::client()->decrypt_group_message(session.get(), e.content.ciphertext); - msg_str = std::string((char *)res.data.data(), res.data.size()); - } catch (const lmdb::error &e) { - nhlog::db()->critical("failed to retrieve megolm session with index ({}, {}, {})", - index.room_id, - index.session_id, - index.sender_key, - e.what()); - dummy.content.body = - tr("-- Decryption Error (failed to retrieve megolm keys from db) --", - "Placeholder, when the message can't be decrypted, because the DB " - "access " - "failed.") - .toStdString(); - return asCacheEntry(std::move(dummy)); - } catch (const mtx::crypto::olm_exception &e) { - nhlog::crypto()->critical("failed to decrypt message with index ({}, {}, {}): {}", - index.room_id, - index.session_id, - index.sender_key, - e.what()); - dummy.content.body = - tr("-- Decryption Error (%1) --", - "Placeholder, when the message can't be decrypted. In this case, the " - "Olm " - "decrytion returned an error, which is passed as %1.") - .arg(e.what()) - .toStdString(); - return asCacheEntry(std::move(dummy)); - } - - // Add missing fields for the event. - json body = json::parse(msg_str); - body["event_id"] = e.event_id; - body["sender"] = e.sender; - body["origin_server_ts"] = e.origin_server_ts; - body["unsigned"] = e.unsigned_data; - - // relations are unencrypted in content... - mtx::common::add_relations(body["content"], e.content.relations); - - json event_array = json::array(); - event_array.push_back(body); - - std::vector temp_events; - mtx::responses::utils::parse_timeline_events(event_array, temp_events); - - if (temp_events.size() == 1) { - auto encInfo = mtx::accessors::file(temp_events[0]); - - if (encInfo) - emit newEncryptedImage(encInfo.value()); - - return asCacheEntry(std::move(temp_events[0])); + return asCacheEntry(std::move(decryptionResult)); } auto encInfo = mtx::accessors::file(decryptionResult.event.value()); if (encInfo) emit newEncryptedImage(encInfo.value()); - return asCacheEntry(std::move(decryptionResult.event.value())); + return asCacheEntry(std::move(decryptionResult)); +} + +void +EventStore::requestSession(const mtx::events::EncryptedEvent &ev, + bool manual) +{ + // we may not want to request keys during initial sync and such + if (suppressKeyRequests) + return; + + // TODO: Look in key backup + auto copy = ev; + copy.room_id = room_id_; + if (pending_key_requests.count(ev.content.session_id)) { + auto &r = pending_key_requests.at(ev.content.session_id); + r.events.push_back(copy); + + // automatically request once every 10 min, manually every 1 min + qint64 delay = manual ? 60 : (60 * 10); + if (r.requested_at + delay < QDateTime::currentSecsSinceEpoch()) { + r.requested_at = QDateTime::currentSecsSinceEpoch(); + olm::send_key_request_for(copy, r.request_id); + } + } else { + PendingKeyRequests request; + request.request_id = "key_request." + http::client()->generate_txn_id(); + request.requested_at = QDateTime::currentSecsSinceEpoch(); + request.events.push_back(copy); + olm::send_key_request_for(copy, request.request_id); + pending_key_requests[ev.content.session_id] = request; + } } void @@ -877,15 +796,56 @@ EventStore::get(std::string id, std::string_view related_to, bool decrypt, bool events_by_id_.insert(index, event_ptr); } - if (decrypt) + if (decrypt) { if (auto encrypted = std::get_if>( - event_ptr)) - return decryptEvent(index, *encrypted); + event_ptr)) { + auto decrypted = decryptEvent(index, *encrypted); + if (decrypted->event) + return &*decrypted->event; + } + } return event_ptr; } +olm::DecryptionErrorCode +EventStore::decryptionError(std::string id) +{ + if (this->thread() != QThread::currentThread()) + nhlog::db()->warn("{} called from a different thread!", __func__); + + if (id.empty()) + return olm::DecryptionErrorCode::NoError; + + IdIndex index{room_id_, std::move(id)}; + auto edits_ = edits(index.id); + if (!edits_.empty()) { + index.id = mtx::accessors::event_id(edits_.back()); + auto event_ptr = + new mtx::events::collections::TimelineEvents(std::move(edits_.back())); + events_by_id_.insert(index, event_ptr); + } + + auto event_ptr = events_by_id_.object(index); + if (!event_ptr) { + auto event = cache::client()->getEvent(room_id_, index.id); + if (!event) { + return olm::DecryptionErrorCode::NoError; + } + event_ptr = new mtx::events::collections::TimelineEvents(std::move(event->data)); + events_by_id_.insert(index, event_ptr); + } + + if (auto encrypted = + std::get_if>(event_ptr)) { + auto decrypted = decryptEvent(index, *encrypted); + return decrypted->error; + } + + return olm::DecryptionErrorCode::NoError; +} + void EventStore::fetchMore() { diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h index 7c404102..59c1c7c0 100644 --- a/src/timeline/EventStore.h +++ b/src/timeline/EventStore.h @@ -15,6 +15,7 @@ #include #include +#include "Olm.h" #include "Reaction.h" class EventStore : public QObject @@ -78,6 +79,9 @@ public: mtx::events::collections::TimelineEvents *get(int idx, bool decrypt = true); QVariantList reactions(const std::string &event_id); + olm::DecryptionErrorCode decryptionError(std::string id); + void requestSession(const mtx::events::EncryptedEvent &ev, + bool manual); int size() const { @@ -119,7 +123,7 @@ public slots: private: std::vector edits(const std::string &event_id); - mtx::events::collections::TimelineEvents *decryptEvent( + olm::DecryptionResult *decryptEvent( const IdIndex &idx, const mtx::events::EncryptedEvent &e); void handle_room_verification(mtx::events::collections::TimelineEvents event); @@ -129,7 +133,7 @@ private: uint64_t first = std::numeric_limits::max(), last = std::numeric_limits::max(); - static QCache decryptedEvents_; + static QCache decryptedEvents_; static QCache events_; static QCache events_by_id_; @@ -137,6 +141,7 @@ private: { std::string request_id; std::vector> events; + qint64 requested_at; }; std::map pending_key_requests; diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 10d9788d..99e00a67 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -452,6 +452,7 @@ TimelineModel::roleNames() const {IsEditable, "isEditable"}, {IsEncrypted, "isEncrypted"}, {Trustlevel, "trustlevel"}, + {EncryptionError, "encryptionError"}, {ReplyTo, "replyTo"}, {Reactions, "reactions"}, {RoomId, "roomId"}, @@ -639,6 +640,9 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r return crypto::Trust::Unverified; } + case EncryptionError: + return events.decryptionError(event_id(event)); + case ReplyTo: return QVariant(QString::fromStdString(relations(event).reply_to().value_or(""))); case Reactions: { @@ -690,6 +694,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r m.insert(names[RoomName], data(event, static_cast(RoomName))); m.insert(names[RoomTopic], data(event, static_cast(RoomTopic))); m.insert(names[CallType], data(event, static_cast(CallType))); + m.insert(names[EncryptionError], data(event, static_cast(EncryptionError))); return QVariant(m); } @@ -1551,6 +1556,17 @@ TimelineModel::scrollTimerEvent() } } +void +TimelineModel::requestKeyForEvent(QString id) +{ + auto encrypted_event = events.get(id.toStdString(), "", false); + if (encrypted_event) { + if (auto ev = std::get_if>( + encrypted_event)) + events.requestSession(*ev, true); + } +} + void TimelineModel::copyLinkToEvent(QString eventId) const { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index b5c8ca37..ad7cfbbb 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -212,6 +212,7 @@ public: IsEditable, IsEncrypted, Trustlevel, + EncryptionError, ReplyTo, Reactions, RoomId, @@ -264,6 +265,8 @@ public: endResetModel(); } + Q_INVOKABLE void requestKeyForEvent(QString id); + std::vector<::Reaction> reactions(const std::string &event_id) { auto list = events.reactions(event_id); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 76bc127e..b23ed278 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -157,6 +157,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "MtxEvent", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject( + olm::staticMetaObject, "im.nheko", 1, 0, "Olm", "Can't instantiate enum!"); qmlRegisterUncreatableMetaObject( crypto::staticMetaObject, "im.nheko", 1, 0, "Crypto", "Can't instantiate enum!"); qmlRegisterUncreatableMetaObject(verification::staticMetaObject, -- cgit 1.5.1 From b73bd2859ca9c3209f6da9c29346b95548b6b8c9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 7 Aug 2021 23:54:35 +0200 Subject: Protect against replay attacks --- src/Cache.cpp | 6 +++++- src/CacheCryptoStructs.h | 3 +++ src/Olm.cpp | 19 ++++++++++++++++++- src/Olm.h | 4 +++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 6650334a..ee991dc2 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -158,7 +158,7 @@ Cache::isHiddenEvent(lmdb::txn &txn, index.session_id = encryptedEvent->content.session_id; index.sender_key = encryptedEvent->content.sender_key; - auto result = olm::decryptEvent(index, *encryptedEvent); + auto result = olm::decryptEvent(index, *encryptedEvent, true); if (!result.error) e = result.event.value(); } @@ -4294,6 +4294,8 @@ to_json(nlohmann::json &obj, const GroupSessionData &msg) obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain; obj["currently"] = msg.currently; + + obj["indices"] = msg.indices; } void @@ -4307,6 +4309,8 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg) obj.value("forwarding_curve25519_key_chain", std::vector{}); msg.currently = obj.value("currently", SharedWithUsers{}); + + msg.indices = obj.value("indices", std::map()); } void diff --git a/src/CacheCryptoStructs.h b/src/CacheCryptoStructs.h index 409c9d67..69d64885 100644 --- a/src/CacheCryptoStructs.h +++ b/src/CacheCryptoStructs.h @@ -50,6 +50,9 @@ struct GroupSessionData std::string sender_claimed_ed25519_key; std::vector forwarding_curve25519_key_chain; + //! map from index to event_id to check for replay attacks + std::map indices; + // who has access to this session. // Rotate, when a user leaves the room and share, when a user gets added. SharedWithUsers currently; diff --git a/src/Olm.cpp b/src/Olm.cpp index 293b12de..e4ab0aa1 100644 --- a/src/Olm.cpp +++ b/src/Olm.cpp @@ -1028,7 +1028,8 @@ send_megolm_key_to_device(const std::string &user_id, DecryptionResult decryptEvent(const MegolmSessionIndex &index, - const mtx::events::EncryptedEvent &event) + const mtx::events::EncryptedEvent &event, + bool dont_write_db) { try { if (!cache::client()->inboundMegolmSessionExists(index)) { @@ -1043,10 +1044,26 @@ decryptEvent(const MegolmSessionIndex &index, std::string msg_str; try { auto session = cache::client()->getInboundMegolmSession(index); + auto sessionData = + cache::client()->getMegolmSessionData(index).value_or(GroupSessionData{}); auto res = olm::client()->decrypt_group_message(session.get(), event.content.ciphertext); msg_str = std::string((char *)res.data.data(), res.data.size()); + + if (!event.event_id.empty() && event.event_id[0] == '$') { + auto oldIdx = sessionData.indices.find(res.message_index); + if (oldIdx != sessionData.indices.end()) { + if (oldIdx->second != event.event_id) + return {DecryptionErrorCode::ReplayAttack, + std::nullopt, + std::nullopt}; + } else if (!dont_write_db) { + sessionData.indices[res.message_index] = event.event_id; + cache::client()->saveInboundMegolmSession( + index, std::move(session), sessionData); + } + } } catch (const lmdb::error &e) { return {DecryptionErrorCode::DbError, e.what(), std::nullopt}; } catch (const mtx::crypto::olm_exception &e) { diff --git a/src/Olm.h b/src/Olm.h index ac1a1617..ab86ca00 100644 --- a/src/Olm.h +++ b/src/Olm.h @@ -81,9 +81,11 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id, nlohmann::json body); +//! Decrypt an event. Use dont_write_db to prevent db writes when already in a write transaction. DecryptionResult decryptEvent(const MegolmSessionIndex &index, - const mtx::events::EncryptedEvent &event); + const mtx::events::EncryptedEvent &event, + bool dont_write_db = false); crypto::Trust calculate_trust(const std::string &user_id, const std::string &curve25519); -- cgit 1.5.1