diff --git a/TODO.md b/TODO.md index e2a551c0..37efc778 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,7 @@ # TODO +- rename goto*account → scrollto*account +- account number binds - update glass theme - back/front buttons in small window - minimum sizes diff --git a/src/backend/matrix_client.py b/src/backend/matrix_client.py index a1a1d28f..0c24b092 100644 --- a/src/backend/matrix_client.py +++ b/src/backend/matrix_client.py @@ -871,6 +871,7 @@ class MatrixClient(nio.AsyncClient): """ self.models[self.user_id, "rooms"].pop(room_id, None) + self.models["every_room"].pop((self.user_id, room_id), None) self.models.pop((self.user_id, room_id, "events"), None) self.models.pop((self.user_id, room_id, "members"), None) @@ -1209,8 +1210,9 @@ class MatrixClient(nio.AsyncClient): mentions = 0 unreads = 0 - self.models[self.user_id, "rooms"][room.room_id] = Room( + room_item = Room( id = room.room_id, + for_account = self.user_id, given_name = room.name or "", display_name = room.display_name or "", avatar_url = room.gen_avatar_url or "", @@ -1244,9 +1246,11 @@ class MatrixClient(nio.AsyncClient): last_event_date = last_event_date, mentions = mentions, unreads = unreads, - ) + self.models[self.user_id, "rooms"][room.room_id] = room_item + self.models["every_room"][self.user_id, room.room_id] = room_item + # List members that left the room, then remove them from our model left_the_room = [ user_id diff --git a/src/backend/models/items.py b/src/backend/models/items.py index 6db07a28..6f25b597 100644 --- a/src/backend/models/items.py +++ b/src/backend/models/items.py @@ -40,9 +40,9 @@ class Account(ModelItem): first_sync_done: bool = False def __lt__(self, other: "Account") -> bool: - """Sort by display name or user ID.""" - name = self.display_name or self.id[1:] - other_name = other.display_name or other.id[1:] + """Sort by user ID.""" + name = self.id[1:] + other_name = other.id[1:] return name.lower() < other_name.lower() @@ -51,6 +51,7 @@ class Room(ModelItem): """A matrix room we are invited to, are or were member of.""" id: str = field() + for_account: str = field() given_name: str = "" display_name: str = "" main_alias: str = "" @@ -90,19 +91,22 @@ class Room(ModelItem): Invited rooms are first, then joined rooms, then left rooms. Within these categories, sort by last event date (room with recent - messages are first), then by display names, but + messages are first), then by display names, then account, but keep rooms with mentions on top, followed by rooms with unread events. """ # Left rooms may still have an inviter_id, so check left first. return ( + self.for_account, self.left, other.inviter_id, bool(other.mentions), bool(other.unreads), other.last_event_date, (self.display_name or self.id).lower(), + ) < ( + other.for_account, other.left, self.inviter_id, bool(self.mentions), diff --git a/src/gui/DebugConsole.qml b/src/gui/DebugConsole.qml index 8a010cc9..f55e94c5 100644 --- a/src/gui/DebugConsole.qml +++ b/src/gui/DebugConsole.qml @@ -217,7 +217,7 @@ HDrawer { NumberAnimation { running: doUselessThing - target: mainUI.mainPane.mainPaneList + target: mainUI.mainPane.roomList property: "rotation" duration: 250 from: 360 @@ -225,19 +225,4 @@ HDrawer { loops: Animation.Infinite onStopped: target.rotation = 0 } - - NumberAnimation { - running: doUselessThing - target: mainUI.pageLoader - property: "scale" - duration: 250 - from: 1 - to: -1 - onStopped: if (doUselessThing) { - [from, to] = [to, from] - start() - } else { - target.scale = 1 - } - } } diff --git a/src/gui/MainPane/Account.qml b/src/gui/MainPane/Account.qml index f3294a64..e6c75ac8 100644 --- a/src/gui/MainPane/Account.qml +++ b/src/gui/MainPane/Account.qml @@ -17,17 +17,19 @@ HTile { HUserAvatar { id: avatar - userId: model.id - displayName: model.display_name - mxc: model.avatar_url + userId: accountModel.id + displayName: accountModel.display_name + mxc: accountModel.avatar_url radius: 0 } TitleLabel { - text: model.display_name || model.id + text: accountModel.display_name || accountModel.id color: hovered ? - utils.nameColor(model.display_name || model.id.substring(1)) : + utils.nameColor( + accountModel.display_name || accountModel.id.substring(1), + ) : theme.accountView.account.name Behavior on color { HColorAnimation {} } @@ -40,7 +42,7 @@ HTile { backgroundColor: "transparent" toolTip.text: qsTr("Add new chat") onClicked: pageLoader.showPage( - "AddChat/AddChat", {userId: model.id}, + "AddChat/AddChat", {userId: accountModel.id}, ) Layout.fillHeight: true @@ -59,7 +61,7 @@ HTile { HMenuItem { icon.name: "copy-user-id" text: qsTr("Copy user ID") - onTriggered: Clipboard.text = model.id + onTriggered: Clipboard.text = accountModel.id } HMenuItemPopupSpawner { @@ -68,17 +70,18 @@ HTile { text: qsTr("Sign out") popup: "Popups/SignOutPopup.qml" - properties: { "userId": model.id } + properties: { "userId": accountModel.id } } } onLeftClicked: { pageLoader.showPage( - "AccountSettings/AccountSettings", { "userId": model.id } + "AccountSettings/AccountSettings", { "userId": accountModel.id } ) } + property var accountModel property bool isCurrent: false diff --git a/src/gui/MainPane/AccountSwipeView.qml b/src/gui/MainPane/AccountSwipeView.qml deleted file mode 100644 index f51e8817..00000000 --- a/src/gui/MainPane/AccountSwipeView.qml +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later - -import QtQuick 2.12 -import ".." -import "../Base" - -HSwipeView { - id: swipeView - orientation: Qt.Vertical - - Repeater { - model: ModelStore.get("accounts") - - AccountView {} - } -} diff --git a/src/gui/MainPane/AccountView.qml b/src/gui/MainPane/AccountView.qml deleted file mode 100644 index 82f05b7a..00000000 --- a/src/gui/MainPane/AccountView.qml +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later - -import QtQuick 2.12 -import QtQuick.Layouts 1.12 -import ".." -import "../Base" - -HLoader { - id: loader - active: - HSwipeView.isCurrentItem || - HSwipeView.isNextItem || - HSwipeView.isPreviousItem - - readonly property bool isCurrent: HSwipeView.isCurrentItem - - sourceComponent: HColumnLayout { - id: column - - readonly property QtObject accountModel: model - readonly property alias roomList: roomList - - Account { - id: account - isCurrent: loader.isCurrent - - Layout.fillWidth: true - } - - RoomList { - id: roomList - clip: true - accountModel: column.accountModel - roomPane: swipeView - isCurrent: loader.isCurrent - - Layout.fillWidth: true - Layout.fillHeight: true - } - - FilterRoomsField { - roomList: roomList - Layout.fillWidth: true - } - } -} diff --git a/src/gui/MainPane/AccountsBar.qml b/src/gui/MainPane/AccountsBar.qml index 34ebcdf8..f2c96868 100644 --- a/src/gui/MainPane/AccountsBar.qml +++ b/src/gui/MainPane/AccountsBar.qml @@ -8,21 +8,21 @@ import "../Base" import "../Base/HTile" HColumnLayout { - property AccountSwipeView accountSwipeView + property RoomList roomList HButton { - id: everyRoomButton - icon.name: "every-room" - toolTip.text: qsTr("Every room") - backgroundColor: theme.accountsBar.everyRoomButtonBackground - // onClicked: pageLoader.showPage("AddAccount/AddAccount") + id: addAccountButton + icon.name: "add-account" + toolTip.text: qsTr("Add another account") + backgroundColor: theme.accountsBar.addAccountButtonBackground + onClicked: pageLoader.showPage("AddAccount/AddAccount") Layout.preferredHeight: theme.baseElementsHeight HShortcut { - sequences: window.settings.keys.showEveryRoom - onActivated: everyRoomButton.clicked() + sequences: window.settings.keys.addNewAccount + onActivated: addAccountButton.clicked() } } @@ -30,10 +30,14 @@ HColumnLayout { id: accountList clip: true model: ModelStore.get("accounts") - currentIndex: accountSwipeView.currentIndex + currentIndex: + roomList.currentIndex === -1 ? + -1 : + model.findIndex( + roomList.model.get(roomList.currentIndex).for_account, -1, + ) highlight: Item { - Rectangle { anchors.fill: parent color: theme.accountsBar.accountList.account.selectedBackground @@ -74,7 +78,7 @@ HColumnLayout { } } - onLeftClicked: accountSwipeView.currentIndex = model.index + onLeftClicked: roomList.goToAccount(model.id) } Layout.fillWidth: true @@ -82,12 +86,18 @@ HColumnLayout { HShortcut { sequences: window.settings.keys.goToPreviousAccount - onActivated: accountSwipeView.decrementWrapIndex() + onActivated: { + accountList.decrementCurrentIndex() + accountList.currentItem.leftClicked() + } } HShortcut { sequences: window.settings.keys.goToNextAccount - onActivated: accountSwipeView.incrementWrapIndex() + onActivated: { + accountList.incrementCurrentIndex() + accountList.currentItem.leftClicked() + } } Rectangle { @@ -97,21 +107,6 @@ HColumnLayout { } } - HButton { - id: addAccountButton - icon.name: "add-account" - toolTip.text: qsTr("Add another account") - backgroundColor: theme.accountsBar.addAccountButtonBackground - onClicked: pageLoader.showPage("AddAccount/AddAccount") - - Layout.preferredHeight: theme.baseElementsHeight - - HShortcut { - sequences: window.settings.keys.addNewAccount - onActivated: addAccountButton.clicked() - } - } - HButton { id: settingsButton backgroundColor: theme.accountsBar.settingsButtonBackground diff --git a/src/gui/MainPane/FilterRoomsField.qml b/src/gui/MainPane/FilterRoomsField.qml index eebcc995..55a6638e 100644 --- a/src/gui/MainPane/FilterRoomsField.qml +++ b/src/gui/MainPane/FilterRoomsField.qml @@ -21,7 +21,7 @@ HTextField { Keys.onEnterPressed: Keys.onReturnPressed(event) Keys.onReturnPressed: { if (window.settings.clearRoomFilterOnEnter) text = "" - roomList.showRoom() + roomList.showRoomAtIndex() } Keys.onEscapePressed: { @@ -36,13 +36,11 @@ HTextField { Behavior on opacity { HNumberAnimation {} } HShortcut { - enabled: loader.isCurrent sequences: window.settings.keys.clearRoomFilter onActivated: filterField.text = "" } HShortcut { - enabled: loader.isCurrent sequences: window.settings.keys.toggleFocusMainPane onActivated: { if (filterField.activeFocus) { diff --git a/src/gui/MainPane/MainPane.qml b/src/gui/MainPane/MainPane.qml index 82dfb8de..2746bc25 100644 --- a/src/gui/MainPane/MainPane.qml +++ b/src/gui/MainPane/MainPane.qml @@ -10,7 +10,9 @@ HDrawer { background: null // minimumSize: bottomBar.addAccountButton.width - // property alias filter: bottomBar.roomFilter + readonly property alias accountBar: accountBar + readonly property alias roomList: roomList + readonly property alias filterRoomsField: filterRoomsField readonly property bool small: width < theme.controls.avatar.size + theme.spacing * 2 @@ -28,17 +30,26 @@ HDrawer { AccountsBar { id: accountBar - accountSwipeView: accountSwipeView + roomList: roomList Layout.fillWidth: false } - AccountSwipeView { - id: accountSwipeView - currentIndex: 0 + HColumnLayout { + RoomList { + id: roomList + clip: true - Layout.fillWidth: true - Layout.fillHeight: true + Layout.fillWidth: true + Layout.fillHeight: true + } + + FilterRoomsField { + id: filterRoomsField + roomList: roomList + + Layout.fillWidth: true + } } } } diff --git a/src/gui/MainPane/Room.qml b/src/gui/MainPane/Room.qml index 447b08b9..a5872ad4 100644 --- a/src/gui/MainPane/Room.qml +++ b/src/gui/MainPane/Room.qml @@ -146,7 +146,7 @@ HTileDelegate { popup: "Popups/InviteToRoomPopup.qml" properties: ({ - userId: userId, + userId: model.for_account, roomId: model.id, roomName: model.display_name, invitingAllowed: Qt.binding(() => model.can_invite) @@ -169,7 +169,7 @@ HTileDelegate { label.textFormat: Text.StyledText onTriggered: py.callClientCoro( - userId, "join", [model.id] + model.for_account, "join", [model.id] ) } @@ -181,7 +181,7 @@ HTileDelegate { popup: "Popups/LeaveRoomPopup.qml" properties: ({ - userId: userId, + userId: model.for_account, roomId: model.id, roomName: model.display_name, }) @@ -195,7 +195,7 @@ HTileDelegate { popup: "Popups/ForgetRoomPopup.qml" autoDestruct: false properties: ({ - userId: userId, + userId: model.for_account, roomId: model.id, roomName: model.display_name, }) @@ -203,14 +203,12 @@ HTileDelegate { } - property string userId - readonly property bool joined: ! invited && ! parted readonly property bool invited: model.inviter_id && ! parted readonly property bool parted: model.left readonly property ListModel eventModel: - ModelStore.get(userId, model.id, "events") + ModelStore.get(model.for_account, model.id, "events") readonly property QtObject lastEvent: eventModel.count > 0 ? eventModel.get(0) : null diff --git a/src/gui/MainPane/RoomList.qml b/src/gui/MainPane/RoomList.qml index 0083bc15..57b9a37a 100644 --- a/src/gui/MainPane/RoomList.qml +++ b/src/gui/MainPane/RoomList.qml @@ -7,44 +7,78 @@ import "../Base" HListView { id: roomList - model: ModelStore.get(accountModel.id, "rooms") + model: ModelStore.get("every_room") + + section.property: "for_account" + section.labelPositioning: + ViewSection.InlineLabels | ViewSection.CurrentLabelAtStart + section.delegate: Account { + accountModel: ModelStore.get("accounts").find(section) + width: roomList.width + } delegate: Room { width: roomList.width - userId: accountModel.id - onActivated: showRoom(model.index) + onActivated: showRoomAtIndex(model.index) } - onIsCurrentChanged: if (isCurrent) showRoom() + + readonly property var sectionIndice: { + const sections = {} + const accounts = ModelStore.get("accounts") + let total = 0 + + for (let i = 0; i < accounts.count; i++) { + const userId = accounts.get(i).id + sections[userId] = total + total += ModelStore.get(userId, "rooms").count + } + + return sections + } - property var accountModel - property var roomPane - property bool isCurrent: false + function goToAccount(userId) { + currentIndex = sectionIndice[userId] + } + function goToAccountNumber(num) { + currentIndex = Object.values(sectionIndice).sort()[num] + } - function showRoom(index=currentIndex) { + function showRoomAtIndex(index=currentIndex) { if (index === -1) index = 0 - if (index >= model.count) return - pageLoader.showRoom(accountModel.id, model.get(index).id) + index = Math.min(index, model.count - 1) + + const room = model.get(index) + pageLoader.showRoom(room.for_account, room.id) currentIndex = index } + function showAccountRoomAtIndex(index) { + const userId = + model.get(currentIndex === -1 ? 0 : currentIndex).for_account + + const rooms = ModelStore.get(userId, "rooms") + if (! rooms.count) return + + const room = rooms.get(utils.numberWrapAt(index, rooms.count)) + showRoomAtIndex(model.findIndex(room.id)) + } + Timer { id: showRoomLimiter interval: 200 - onTriggered: showRoom() + onTriggered: showRoomAtIndex() } HShortcut { - enabled: isCurrent sequences: window.settings.keys.goToPreviousRoom onActivated: { decrementCurrentIndex(); showRoomLimiter.restart() } } HShortcut { - enabled: isCurrent sequences: window.settings.keys.goToNextRoom onActivated: { incrementCurrentIndex(); showRoomLimiter.restart() } } @@ -54,28 +88,13 @@ HListView { Item { HShortcut { - enabled: isCurrent sequence: window.settings.keys.focusRoomAtIndex[modelData] - onActivated: showRoom(parseInt(modelData - 1, 10)) + onActivated: + showAccountRoomAtIndex(parseInt(modelData - 1, 10)) } } } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: { - const goingDown = wheel.angleDelta.y < 0 - - if (! goingDown && roomList.atYBeginning) - roomPane.decrementCurrentIndex() - else if (goingDown && roomList.atYEnd) - roomPane.incrementCurrentIndex() - else - wheel.accepted = false - } - } - Rectangle { anchors.fill: parent z: -100 diff --git a/src/gui/ModelStore.qml b/src/gui/ModelStore.qml index 9a693d40..dd351e85 100644 --- a/src/gui/ModelStore.qml +++ b/src/gui/ModelStore.qml @@ -14,18 +14,18 @@ QtObject { ListModel { property var modelId - function findIndex(id) { + function findIndex(id, default_=null) { for (let i = 0; i < count; i++) if (get(i).id === id) return i - return null + return default_ } - function find(id) { + function find(id, default_=null) { for (let i = 0; i < count; i++) if (get(i).id === id) return get(i) - return null + return default_ } } }