From 902f13ab681bb96bf4134d0a8562418034226c31 Mon Sep 17 00:00:00 2001 From: miruka Date: Tue, 2 Mar 2021 08:53:50 -0400 Subject: [PATCH] Lock position of the room that's focused in GUI When the currently shown page is the chat of a certain room, prevent that room from moving around in the left pane due to new messages/activity or unread/highlight counters change. When the user switches to another page/room, the previously held lock is released and that room completes all the moves it would have done if it wasn't locked. This makes navigating a room list with lots of activity easier, and prevent annoyances like clicking on a room with unread messages and having it immediatly fly down the list (possibly out of scroll view). --- docs/CHANGELOG.md | 5 ++++ src/backend/matrix_client.py | 19 +++++++++++++++ src/backend/models/items.py | 41 ++++++++++++++++++++------------- src/gui/Pages/Chat/ChatPage.qml | 19 ++++++++++++++- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f69220ae..ba41b1d3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -86,6 +86,11 @@ and this project adheres to - Improve room page loading speed +- In the left pane, lock the position of the room corresponding to the + currently visible chat page if any. + This fixes annoyances like clicking on a room with unread messages only to + see it immediatly fly down the list, potentially outside of scrolling view. + - When replying to a message, pressing the reply keybind again while focusing on that message will now cancel the reply diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index 1e130c4a..8311d138 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -2021,6 +2021,21 @@ class MatrixClient(nio.AsyncClient): room.last_event_date = item.date + async def lock_room_position(self, room_id: str, lock: bool) -> None: + """Set wheter a room should try to hold its current sort position.""" + + room = self.models[self.user_id, "rooms"][room_id] + + if not lock: + room._sort_overrides = {} + return + + for k in ("last_event_date", "unreads", "highlights", "local_unreads"): + room._sort_overrides[k] = getattr(room, k) + + room.notify_change("_sort_overrides") + + async def register_nio_room( self, room: nio.MatrixRoom, @@ -2038,6 +2053,7 @@ class MatrixClient(nio.AsyncClient): registered = self.models[self.user_id, "rooms"][room.room_id] except KeyError: registered = None + sort_overrides = {} last_event_date = datetime.fromtimestamp(0) typing_members = [] local_unreads = False @@ -2048,6 +2064,7 @@ class MatrixClient(nio.AsyncClient): self.room_contains_unverified(room.room_id) ) else: + sort_overrides = registered._sort_overrides last_event_date = registered.last_event_date typing_members = registered.typing_members local_unreads = registered.local_unreads @@ -2126,6 +2143,8 @@ class MatrixClient(nio.AsyncClient): lexical_sorting = self.backend.settings.RoomList.lexical_sort, pinned = room.room_id in pinned.get(self.user_id, []), + + _sort_overrides = sort_overrides, ) self.models[self.user_id, "rooms"][room.room_id] = room_item diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 822688c0..29807d78 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -183,6 +183,15 @@ class Room(ModelItem): lexical_sorting: bool = False pinned: bool = False + # Allowed keys: "last_event_date", "unreads", "highlights", "local_unreads" + # Keys in this dict will override their corresponding item fields for the + # __lt__ method. This is used when we want to lock a room's position, + # e.g. to avoid having the room move around when it is focused in the GUI + _sort_overrides: Dict[str, Any] = field(default_factory=dict) + + def _sorting(self, key: str) -> Any: + return self._sort_overrides.get(key, getattr(self, key)) + def __lt__(self, other: "Room") -> bool: by_activity = not self.lexical_sorting @@ -191,10 +200,10 @@ class Room(ModelItem): other.pinned, self.left, # Left rooms may have an inviter_id, check them first bool(other.inviter_id), - bool(by_activity and other.highlights), - bool(by_activity and other.unreads), - bool(by_activity and other.local_unreads), - other.last_event_date if by_activity else ZERO_DATE, + bool(by_activity and other._sorting("highlights")), + bool(by_activity and other._sorting("unreads")), + bool(by_activity and other._sorting("local_unreads")), + other._sorting("last_event_date") if by_activity else ZERO_DATE, (self.display_name or self.id).lower(), self.id, @@ -203,10 +212,10 @@ class Room(ModelItem): self.pinned, other.left, bool(self.inviter_id), - bool(by_activity and self.highlights), - bool(by_activity and self.unreads), - bool(by_activity and self.local_unreads), - self.last_event_date if by_activity else ZERO_DATE, + bool(by_activity and self._sorting("highlights")), + bool(by_activity and self._sorting("unreads")), + bool(by_activity and self._sorting("local_unreads")), + self._sorting("last_event_date") if by_activity else ZERO_DATE, (other.display_name or other.id).lower(), other.id, ) @@ -233,10 +242,10 @@ class AccountOrRoom(Account, Room): other.pinned, self.left, bool(other.inviter_id), - bool(by_activity and other.highlights), - bool(by_activity and other.unreads), - bool(by_activity and other.local_unreads), - other.last_event_date if by_activity else ZERO_DATE, + bool(by_activity and other._sorting("highlights")), + bool(by_activity and other._sorting("unreads")), + bool(by_activity and other._sorting("local_unreads")), + other._sorting("last_event_date") if by_activity else ZERO_DATE, (self.display_name or self.id).lower(), self.id, @@ -247,10 +256,10 @@ class AccountOrRoom(Account, Room): self.pinned, other.left, bool(self.inviter_id), - bool(by_activity and self.highlights), - bool(by_activity and self.unreads), - bool(by_activity and self.local_unreads), - self.last_event_date if by_activity else ZERO_DATE, + bool(by_activity and self._sorting("highlights")), + bool(by_activity and self._sorting("unreads")), + bool(by_activity and self._sorting("local_unreads")), + self._sorting("last_event_date") if by_activity else ZERO_DATE, (other.display_name or other.id).lower(), other.id, ) diff --git a/src/gui/Pages/Chat/ChatPage.qml b/src/gui/Pages/Chat/ChatPage.qml index 54476f91..c224acae 100644 --- a/src/gui/Pages/Chat/ChatPage.qml +++ b/src/gui/Pages/Chat/ChatPage.qml @@ -14,7 +14,9 @@ HColumnPage { id: chatPage property string loadMembersFutureId: "" + property var lockedRoom: null // null or [userId, roomId] + readonly property var userRoomId: chat.userRoomId readonly property alias roomHeader: roomHeader readonly property alias eventList: eventList readonly property alias typingMembers: typingMembers @@ -30,11 +32,26 @@ HColumnPage { anchors.fill: parent } + function lockRoomPosition(lock) { + if (lock && lockedRoom) py.callClientCoro( + lockedRoom[0], "lock_room_position", [lockedRoom[1], false], + ) + + lockedRoom = lock ? [chat.userId, chat.roomId] : null + py.callClientCoro( + chat.userId, "lock_room_position", [chat.roomId, lock], + ) + } + + padding: 0 column.spacing: 0 - Component.onDestruction: + onUserRoomIdChanged: lockRoomPosition(true) + Component.onDestruction: { + lockRoomPosition(false) if (loadMembersFutureId) py.cancelCoro(loadMembersFutureId) + } Timer { interval: 200