From 93404559b995f831e17ab3b8d142b64658068660 Mon Sep 17 00:00:00 2001 From: miruka Date: Wed, 14 Apr 2021 13:53:07 -0400 Subject: [PATCH] Add ignored users list to account settings --- src/backend/matrix_client.py | 43 ++++---- src/backend/models/items.py | 3 +- src/backend/nio_callbacks.py | 5 +- src/gui/Pages/AccountSettings/General.qml | 118 ++++++++++++++++++---- 4 files changed, 127 insertions(+), 42 deletions(-) diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index fa20a0a3..b39541c5 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -525,13 +525,10 @@ class MatrixClient(nio.AsyncClient): await asyncio.sleep(0.2) - async def ignore_user(self, user_id: str, ignore: bool) -> None: - ignored = self.ignored_user_ids.copy() - - if ignore: - ignored.add(user_id) - else: - ignored.discard(user_id) + async def set_ignored_users(self, *user_ids: str) -> None: + previous_ignored = self.ignored_user_ids + now_ignored = set(user_ids) + no_longer_ignored = previous_ignored - now_ignored path = ["user", self.user_id, "account_data", "m.ignored_user_list"] params = {"access_token": self.access_token} @@ -540,18 +537,18 @@ class MatrixClient(nio.AsyncClient): nio.responses.EmptyResponse, "PUT", nio.Api._build_path(path, params), - nio.Api.to_json({"ignored_users": {u: {} for u in ignored}}), + nio.Api.to_json({"ignored_users": {u: {} for u in now_ignored}}), ) # Invites and messages from ignored users won't be returned anymore on - # syncs, thus will be absent on client restart. Clean up immediatly, - # and also update Member.ignored fields: + # syncs, thus will be absent on client restart. + # Clean up immediatly, and also update Member.ignored fields: room_model = self.models[self.user_id, "rooms"] with room_model.batch_remove(): for room_id, room in room_model.copy().items(): - if ignore and room.inviter_id == user_id: + if room.inviter_id in now_ignored: self.ignored_rooms.add(room_id) del room_model[room_id] self.models.pop((self.user_id, room_id, "events"), None) @@ -561,18 +558,28 @@ class MatrixClient(nio.AsyncClient): event_model = self.models[self.user_id, room_id, "events"] member_model = self.models[self.user_id, room_id, "members"] - if user_id in member_model: - member_model[user_id].ignored = ignore + for user_id in now_ignored: + if user_id in member_model: + member_model[user_id].ignored = True - if ignore: - with event_model.batch_remove(): - for event_id, event in event_model.copy().items(): - if event.sender_id == user_id: - del event_model[event_id] + for user_id in no_longer_ignored: + if user_id in member_model: + member_model[user_id].ignored = False + + with event_model.batch_remove(): + for event_id, event in event_model.copy().items(): + if event.sender_id in now_ignored: + del event_model[event_id] await self.update_account_unread_counts() + async def ignore_user(self, user_id: str, ignore: bool) -> None: + current = self.ignored_user_ids + new = current | {user_id} if ignore else current - {user_id} + await self.set_ignored_users(*new) + + async def can_kick(self, room_id: str, target_user_id: str) -> bool: """Return whether we can kick a certain user in a room.""" diff --git a/src/backend/models/items.py b/src/backend/models/items.py index cd81b94c..72752a95 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -7,7 +7,7 @@ import json from dataclasses import asdict, dataclass, field from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union from uuid import UUID import lxml # nosec @@ -79,6 +79,7 @@ class Account(ModelItem): total_unread: int = 0 total_highlights: int = 0 local_unreads: bool = False + ignored_users: Set[str] = field(default_factory=set) # For some reason, Account cannot inherit Presence, because QML keeps # complaining type error on unknown file diff --git a/src/backend/nio_callbacks.py b/src/backend/nio_callbacks.py index 30335ad7..55f1ec30 100644 --- a/src/backend/nio_callbacks.py +++ b/src/backend/nio_callbacks.py @@ -861,8 +861,9 @@ class NioCallbacks: ) -> None: if ev.type == "m.ignored_user_list": - user_ids = set(ev.content.get("ignored_users", {})) - self.client.ignored_user_ids = user_ids + users = set(ev.content.get("ignored_users", {})) + self.client.ignored_user_ids = users + self.models["accounts"][self.client.user_id].ignored_users = users # Presence event callbacks diff --git a/src/gui/Pages/AccountSettings/General.qml b/src/gui/Pages/AccountSettings/General.qml index d7e945db..d55c637e 100644 --- a/src/gui/Pages/AccountSettings/General.qml +++ b/src/gui/Pages/AccountSettings/General.qml @@ -21,25 +21,6 @@ HFlickableColumnPage { } function applyChanges() { - if (nameField.item.changed) { - saveButton.nameChangeRunning = true - - py.callClientCoro( - userId, "set_displayname", [nameField.item.text], () => { - py.callClientCoro(userId, "update_own_profile", [], () => { - saveButton.nameChangeRunning = false - }) - } - ) - } - - if (aliasFieldItem.changed) { - window.settings.Chat.Composer.Aliases[userId] = - aliasFieldItem.text - - window.saveSettings() - } - if (avatar.changed) { saveButton.avatarChangeRunning = true @@ -50,11 +31,39 @@ HFlickableColumnPage { py.callClientCoro(userId, "update_own_profile", [], () => { saveButton.avatarChangeRunning = false }) + }, (errType, [httpCode]) => { console.error("Avatar upload failed:", httpCode, errType) saveButton.avatarChangeRunning = false }) } + + if (nameField.item.changed) { + saveButton.nameChangeRunning = true + const name = [nameField.item.text] + + py.callClientCoro(userId, "set_displayname", [name] , () => { + py.callClientCoro(userId, "update_own_profile", [], () => { + saveButton.nameChangeRunning = false + }) + }) + } + + if (aliasFieldItem.changed) { + window.settings.Chat.Composer.Aliases[userId] = + aliasFieldItem.text + + window.saveSettings() + } + + if (ignoredUsersAreaItem.changed) { + saveButton.ignoredUsersChangeRunning = true + const users = ignoredUsersAreaItem.userIds + + py.callClientCoro(userId, "set_ignored_users", users, () => { + saveButton.ignoredUsersChangeRunning = false + }) + } } function cancel() { @@ -80,13 +89,19 @@ HFlickableColumnPage { property bool nameChangeRunning: false property bool avatarChangeRunning: false + property bool ignoredUsersChangeRunning: false disableWhileLoading: false - loading: nameChangeRunning || avatarChangeRunning + loading: + nameChangeRunning || + avatarChangeRunning || + ignoredUsersChangeRunning + enabled: avatar.changed || nameField.item.changed || - (aliasFieldItem.changed && ! aliasFieldItem.error) + (aliasFieldItem.changed && ! aliasFieldItem.error) || + (ignoredUsersAreaItem.changed && ! ignoredUsersAreaItem.error) onClicked: applyChanges() } @@ -314,4 +329,65 @@ HFlickableColumnPage { } } } + + HLabeledItem { + id: ignoredUsers + + readonly property var userIds: + ! ignoredUsersAreaItem.text.trim() ? + [] : + ignoredUsersAreaItem.text.trim().split(/\s+/) + + readonly property var invalidUserIds: { + const result = [] + + for (const user of userIds) + if (! /@.+:.+/.test(user)) + result.push(user) + + return result + } + + loading: ! ready + label.text: qsTr("Ignored users:") + errorLabel.text: + invalidUserIds.length ? + qsTr("Incomplete user ID: %1").arg(invalidUserIds.join(", ")) : + "" + + Layout.fillWidth: true + + HRowLayout { + width: parent.width + + HTextArea { + id: ignoredUsersAreaItem + error: ignoredUsers.invalidUserIds.length > 0 + focusItemOnTab: ignoredUsersHelpButton + placeholderText: qsTr("@user1:example.org @user2:ex.org") + defaultText: + ready ? + JSON.parse(account.ignored_users).sort().join(" ") : + "" + + Layout.fillWidth: true + Layout.fillHeight: true + } + + FieldHelpButton { + id: ignoredUsersHelpButton + helpText: qsTr( + "List of user IDs, separated by a space, from which you " + + "will not receive messages or room invites.\n\n" + + + "Their display name, avatar and online status will also " + + "be hidden from room member lists.\n\n" + + + "When removing an user from the ignore list, restarting " + + "%1 is needed to receive anything they might have sent " + + "while being ignored." + ).arg(Qt.application.displayName) + } + } + } }