diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 509b8086..fa20a0a3 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -543,29 +543,32 @@ class MatrixClient(nio.AsyncClient): nio.Api.to_json({"ignored_users": {u: {} for u in ignored}}), ) - if not ignore: - return - # Invites and messages from ignored users won't be returned anymore on - # syncs, thus will be absent on client restart. Clean up immediatly: + # 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 room.inviter_id == user_id: + if ignore and room.inviter_id == user_id: self.ignored_rooms.add(room_id) del room_model[room_id] self.models.pop((self.user_id, room_id, "events"), None) self.models.pop((self.user_id, room_id, "members"), None) continue - event_model = self.models[self.user_id, room_id, "events"] + event_model = self.models[self.user_id, room_id, "events"] + member_model = self.models[self.user_id, room_id, "members"] - 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] + if user_id in member_model: + member_model[user_id].ignored = ignore + + 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] await self.update_account_unread_counts() @@ -2251,6 +2254,7 @@ class MatrixClient(nio.AsyncClient): if member.display_name else "", avatar_url = member.avatar_url or "", typing = user_id in room.typing_users, + ignored = user_id in self.ignored_user_ids, power_level = member.power_level, invited = member.invited, last_read_event = last_read_event, diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 38b89964..cd81b94c 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -275,6 +275,7 @@ class Member(ModelItem): typing: bool = False power_level: int = 0 invited: bool = False + ignored: bool = False profile_updated: datetime = ZERO_DATE last_read_event: str = "" @@ -287,13 +288,15 @@ class Member(ModelItem): return ( self.invited, other.power_level, - self.presence, + self.ignored, + Presence.State.offline if self.ignored else self.presence, (self.display_name or self.id[1:]).lower(), self.id, ) < ( other.invited, self.power_level, - other.presence, + other.ignored, + Presence.State.offline if other.ignored else other.presence, (other.display_name or other.id[1:]).lower(), other.id, ) diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml index d4a4285d..c920c8bf 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml @@ -16,7 +16,9 @@ HTile { backgroundColor: theme.chat.roomPane.listView.member.background contentOpacity: - model.invited ? theme.chat.roomPane.listView.member.invitedOpacity : 1 + model.invited || model.ignored ? + theme.chat.roomPane.listView.member.invitedOpacity : + 1 contentItem: ContentRow { tile: member @@ -25,12 +27,12 @@ HTile { id: avatar clientUserId: chat.userId userId: model.id - displayName: model.display_name - mxc: model.avatar_url + displayName: model.ignored ? "" : model.display_name + mxc: model.ignored ? "" : model.avatar_url powerLevel: model.power_level invited: model.invited compact: member.compact - presence: model.presence + presence: model.ignored ? "offline" : model.presence shiftMembershipIconPositionBy: roomPane.width >= width + 8 * 3 ? -8 : -4 } @@ -40,11 +42,17 @@ HTile { spacing: theme.spacing TitleLabel { - text: model.display_name || model.id + text: + model.ignored ? + model.id : + (model.display_name || model.id) + color: member.colorName ? utils.nameColor( - model.display_name || model.id.substring(1) + model.ignored ? + model.id : + (model.display_name || model.id.substring(1)) ) : theme.chat.roomPane.listView.member.name @@ -54,7 +62,7 @@ HTile { TitleRightInfoLabel { id: lastActiveAt tile: member - visible: presenceTimer.running + visible: ! model.ignored && presenceTimer.running hideUnderWidth: 130 } } @@ -62,7 +70,10 @@ HTile { SubtitleLabel { tile: member color: theme.chat.roomPane.listView.member.subtitle - text: utils.escapeHtml(model.status_msg.trim()) || model.id + text: + model.ignored ? + qsTr("Ignored") : + (utils.escapeHtml(model.status_msg.trim()) || model.id) } HoverHandler { id: nameHover } @@ -72,7 +83,7 @@ HTile { text: model.id + ( - model.status_msg.trim() ? + ! model.ignored && model.status_msg.trim() ? " - " + model.status_msg.trim() : "" ) @@ -80,12 +91,15 @@ HTile { Timer { id: presenceTimer + repeat: true running: + ! model.ignored && ! model.currently_active && model.last_active_at > new Date(1) - repeat: true + interval: new Date() - model.last_active_at < 60000 ? 10000 : 60000 + triggeredOnStart: true onTriggered: lastActiveAt.text = Qt.binding(() => utils.formatRelativeTime(new Date() - model.last_active_at) @@ -101,6 +115,24 @@ HTile { onTriggered: Clipboard.text = model.id } + HMenuItemPopupSpawner { + icon.name: model.ignored ? "stop-ignore-user" : "ignore-user" + icon.color: + model.ignored ? + theme.colors.positiveBackground : + theme.colors.negativeBackground + + text: model.ignored ? qsTr("Stop ignoring") : qsTr("Ignore") + + popup: "Popups/IgnoreUserPopup.qml" + properties: ({ + userId: chat.userId, + targetUserId: model.id, + targetDisplayName: model.display_name, + ignore: ! model.ignored + }) + } + HMenuItemPopupSpawner { property bool permissionToKick: false diff --git a/src/gui/Popups/IgnoreUserPopup.qml b/src/gui/Popups/IgnoreUserPopup.qml new file mode 100644 index 00000000..806a3ddc --- /dev/null +++ b/src/gui/Popups/IgnoreUserPopup.qml @@ -0,0 +1,65 @@ +// Copyright Mirage authors & contributors +// SPDX-License-Identifier: LGPL-3.0-or-later + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import "../Base" +import "../Base/Buttons" + +HFlickableColumnPopup { + id: root + + property string userId + property string targetUserId + property string targetDisplayName + property bool ignore + + function apply() { + py.callClientCoro(userId, "ignore_user", [targetUserId, ignore]) + root.close() + } + + page.footer: AutoDirectionLayout { + ApplyButton { + id: ignoreButton + icon.name: root.ignore ? "ignore-user" : "stop-ignore-user" + text: root.ignore ? qsTr("Ignore") : qsTr("Stop ignoring") + onClicked: root.apply() + } + + CancelButton { + onClicked: root.close() + } + } + + onOpened: ignoreButton.forceActiveFocus() + + SummaryLabel { + readonly property string userText: + utils.coloredNameHtml(root.targetDisplayName, root.targetUserId) + + textFormat: Text.StyledText + text: + root.ignore ? + qsTr("Ignore %1?").arg(userText) : + qsTr("Stop ignoring %1?").arg(userText) + } + + DetailsLabel { + text: + root.ignore ? qsTr( + "You will no longer see their messages and invites.\n\n" + + + "Their name, avatar and online status will also be hidden " + + "in room member lists." + ) : qsTr( + "You will receive their messages and room invites again.\n\n" + + + "Their names, avatar and online status will also become " + + "visible in room member lists.\n\n" + + + "After restarting %1, any message or room invite they had " + + "sent while being ignored will become visible." + ).arg(Qt.application.displayName) + } +} diff --git a/src/icons/thin/ignore-user.svg b/src/icons/thin/ignore-user.svg new file mode 100644 index 00000000..209be9cb --- /dev/null +++ b/src/icons/thin/ignore-user.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/thin/stop-ignore-user.svg b/src/icons/thin/stop-ignore-user.svg new file mode 100644 index 00000000..28199970 --- /dev/null +++ b/src/icons/thin/stop-ignore-user.svg @@ -0,0 +1,3 @@ + + +