From a0a49b6c2a3cf48cb4e979a92cd2065a51bff775 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 24 Oct 2023 01:12:01 +0200 Subject: [PATCH] Cleanup ignore user functionality slightly --- resources/qml/dialogs/UserProfile.qml | 9 ++--- src/ChatPage.cpp | 53 ++++++++++++++++++++++-- src/timeline/TimelineModel.cpp | 12 ------ src/timeline/TimelineModel.h | 6 --- src/timeline/TimelineViewManager.h | 3 +- src/ui/UserProfile.cpp | 58 +++++++++++++++++++-------- src/ui/UserProfile.h | 7 +++- 7 files changed, 102 insertions(+), 46 deletions(-) diff --git a/resources/qml/dialogs/UserProfile.qml b/resources/qml/dialogs/UserProfile.qml index 3d65d52a..989c2bab 100644 --- a/resources/qml/dialogs/UserProfile.qml +++ b/resources/qml/dialogs/UserProfile.qml @@ -295,11 +295,10 @@ ApplicationWindow { image: ":/icons/icons/ui/volume-off-indicator.svg" hoverEnabled: true ToolTip.visible: hovered - ToolTip.text: qsTr("Ignore the user.") - onClicked: { - profile.ignoredStatus(profile.userid, true) - } - visible: !profile.isSelf && !profile.isGlobalUserProfile + ToolTip.text: profile.ignored ? qsTr("Unignore the user.") : qsTr("Ignore the user.") + buttonTextColor: profile.ignored ? Nheko.theme.red : palette.buttonText + onClicked: profile.ignored = !profile.ignored + visible: !profile.isSelf } ImageButton { diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 90d542dd..e63135a9 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include + #include #include "AvatarProvider.h" @@ -21,7 +24,6 @@ #include "encryption/DeviceVerificationFlow.h" #include "encryption/Olm.h" #include "ui/RoomSummary.h" -#include "ui/Theme.h" #include "ui/UserProfile.h" #include "voip/CallManager.h" @@ -29,8 +31,6 @@ #include "timeline/TimelineViewManager.h" -#include "blurhash.hpp" - ChatPage *ChatPage::instance_ = nullptr; static constexpr int CHECK_CONNECTIVITY_INTERVAL = 15'000; static constexpr int RETRY_TIMEOUT = 5'000; @@ -765,6 +765,23 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string // Ensure that we have enough one-time keys available. ensureOneTimeKeyCount(res.device_one_time_keys_count, res.device_unused_fallback_key_types); + std::optional oldIgnoredUsers; + if (auto ignoreEv = std::ranges::find_if( + res.account_data.events, + [](const mtx::events::collections::RoomAccountDataEvents &e) { + return std::holds_alternative< + mtx::events::AccountDataEvent>(e); + }); + ignoreEv != res.account_data.events.end()) { + if (auto oldEv = cache::client()->getAccountData(mtx::events::EventType::IgnoredUsers)) + oldIgnoredUsers = + std::get>( + *oldEv) + .content; + else + oldIgnoredUsers = mtx::events::account_data::IgnoredUsers{}; + } + // TODO: fine grained error handling try { cache::client()->saveState(res); @@ -773,6 +790,36 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); emit syncUI(std::move(res)); + + // if the ignored users changed, clear timeline of all affected rooms. + if (oldIgnoredUsers) { + if (auto newEv = + cache::client()->getAccountData(mtx::events::EventType::IgnoredUsers)) { + std::vector changedUsers{}; + std::ranges::set_symmetric_difference( + oldIgnoredUsers->users, + std::get>( + *newEv) + .content.users, + std::back_inserter(changedUsers), + {}, + &mtx::events::account_data::IgnoredUser::id, + &mtx::events::account_data::IgnoredUser::id); + + std::unordered_set roomsToReload; + for (const auto &user : changedUsers) { + auto commonRooms = cache::client()->getCommonRooms(user.id); + for (const auto &room : commonRooms) + roomsToReload.insert(room.first); + } + + for (const auto &room : roomsToReload) { + if (auto model = + view_manager_->rooms()->getRoomById(QString::fromStdString(room))) + model->clearTimeline(); + } + } + } } catch (const lmdb::map_full_error &e) { nhlog::db()->error("lmdb is full: {}", e.what()); cache::deleteOldData(); diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index d85a9516..e8a0a507 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -521,8 +521,6 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj cache::client()->updateState(room_id_.toStdString(), events_, true); this->syncState({std::move(events_.events)}); }); - - connect(this, &TimelineModel::ignoredUser, this, &TimelineModel::handleIgnoredUser); } QHash @@ -2222,16 +2220,6 @@ TimelineModel::scrollTimerEvent() } } -void -TimelineModel::handleIgnoredUser(const QString &id, const std::optional &err) -{ - if (err) { - MainWindow::instance()->showNotification(tr("Failed to ignore \"%1\": %2").arg(id, *err)); - } else { - this->clearTimeline(); - } -} - void TimelineModel::requestKeyForEvent(const QString &id) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index eefe921f..4ffd61ec 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -18,8 +18,6 @@ #include "CacheStructs.h" #include "EventStore.h" #include "InputBar.h" -#include "InviteesModel.h" -#include "MemberList.h" #include "Permissions.h" #include "ReadReceiptsModel.h" #include "ui/RoomSummary.h" @@ -463,7 +461,6 @@ public slots: private slots: void addPendingMessage(mtx::events::collections::TimelineEvents event); void scrollTimerEvent(); - void handleIgnoredUser(const QString &id, const std::optional &err); signals: void dataAtIdChanged(QString id); @@ -513,9 +510,6 @@ signals: void fetchedMore(); - // The user may close the profile window before we receive a response, so handle it here - void ignoredUser(const QString &id, const std::optional &err); - private: template void sendEncryptedMessage(mtx::events::RoomEvent msg, mtx::events::EventType eventType); diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 6a825b6f..b4e176cd 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -11,7 +11,8 @@ #include #include -#include "ReadReceiptsModel.h" +#include "InviteesModel.h" +#include "MemberList.h" #include "timeline/CommunitiesModel.h" #include "timeline/PresenceEmitter.h" #include "timeline/RoomlistModel.h" diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 3b2375ad..1b66a97d 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -11,11 +11,11 @@ #include "Cache_p.h" #include "ChatPage.h" #include "Logging.h" +#include "MainWindow.h" +#include "MatrixClient.h" #include "UserProfile.h" #include "Utils.h" -#include "encryption/DeviceVerificationFlow.h" #include "encryption/VerificationManager.h" -#include "mtx/responses/crypto.hpp" #include "timeline/TimelineModel.h" #include "timeline/TimelineViewManager.h" #include "ui/UIA.h" @@ -64,6 +64,19 @@ UserProfile::UserProfile(const QString &roomid, new RoomInfoModel(cache::client()->getCommonRooms(userid.toStdString()), this); else sharedRooms_ = new RoomInfoModel({}, this); + + connect(ChatPage::instance(), &ChatPage::syncUI, this, [this](const mtx::responses::Sync &res) { + if (auto ignoreEv = std::ranges::find_if( + res.account_data.events, + [](const mtx::events::collections::RoomAccountDataEvents &e) { + return std::holds_alternative< + mtx::events::AccountDataEvent>(e); + }); + ignoreEv != res.account_data.events.end()) { + // doesn't matter much if it was actually us + emit ignoredChanged(); + } + }); } QHash @@ -224,34 +237,45 @@ UserProfile::refreshDevices() fetchDeviceList(this->userid_); } +bool +UserProfile::ignored() const +{ + auto old = TimelineViewManager::instance()->getIgnoredUsers(); + return old.contains(userid_); +} + void -UserProfile::ignoredStatus(const QString &id, const bool ignore) +UserProfile::setIgnored(bool ignore) { auto old = TimelineViewManager::instance()->getIgnoredUsers(); if (ignore) { - if (old.contains(id)) { - emit this->room()->ignoredUser(id, tr("Already ignored")); + if (old.contains(userid_)) { + emit ignoredChanged(); return; } - old.append(id); + old.append(userid_); } else { - old.removeOne(id); + if (!old.contains(userid_)) { + emit ignoredChanged(); + return; + } + old.removeAll(userid_); } std::vector content; - for (const QString &item : old) { - const mtx::events::account_data::IgnoredUser data{.id = item.toStdString()}; - content.push_back(data); + for (const QString &item : std::as_const(old)) { + content.emplace_back(item.toStdString()); } - const mtx::events::account_data::IgnoredUsers payload{.users{content}}; + mtx::events::account_data::IgnoredUsers payload{.users{content}}; - http::client()->put_account_data(payload, [this, id, ignore](mtx::http::RequestErr e) { - if (ignore) { - emit this->room()->ignoredUser( - id, e ? std::optional(QString::fromStdString(e->matrix_error.error)) : std::nullopt); - } else if (e) { - emit this->unignoredUserError(id, QString::fromStdString(e->matrix_error.error)); + auto userid = userid_; + + http::client()->put_account_data(payload, [userid](mtx::http::RequestErr e) { + if (e) { + MainWindow::instance()->showNotification( + tr("Failed to ignore \"%1\": %2") + .arg(userid, QString::fromStdString(e->matrix_error.error))); } }); } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 1affe8bd..bc5b6a35 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -157,6 +157,7 @@ class UserProfile final : public QObject Q_PROPERTY(int userVerified READ getUserStatus NOTIFY userStatusChanged) Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged) Q_PROPERTY(bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged) + Q_PROPERTY(bool ignored READ ignored WRITE setIgnored NOTIFY ignoredChanged) Q_PROPERTY(bool isSelf READ isSelf CONSTANT) Q_PROPERTY(TimelineModel *room READ room CONSTANT) public: @@ -184,7 +185,6 @@ public: Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void banUser(); Q_INVOKABLE void signOutDevice(const QString &deviceID); - Q_INVOKABLE void ignoredStatus(const QString &id, const bool ignore); Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); Q_INVOKABLE void startChat(bool encryptionEnabled); @@ -193,6 +193,9 @@ public: Q_INVOKABLE void changeAvatar(); Q_INVOKABLE void openGlobalProfile(); + void setIgnored(bool ignored); + bool ignored() const; + signals: void userStatusChanged(); void loadingChanged(); @@ -201,7 +204,7 @@ signals: void displayError(const QString &errorMessage); void globalUsernameRetrieved(const QString &globalUser); void devicesChanged(); - void unignoredUserError(const QString &id, const QVariant &err); + void ignoredChanged(); // internal void verificationStatiChanged();