From 3ccff4f337e8ce86e151eba7347e4ebb60814f7d Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Fri, 31 Mar 2023 16:36:15 +0200 Subject: [PATCH] Refactor room list in multiple sub components Also modernize the codebase where possible (e.g use required properties, reorder properties, fix warnings, don't use Action when not needed) Signed-off-by: Carl Schwan --- src/qml/Dialog/ConfirmLogoutDialog.qml | 4 +- .../{Menu => Page/RoomList}/AccountMenu.qml | 3 +- .../Page/RoomList/CollapsedRoomDelegate.qml | 53 ++ .../RoomList/ContextMenu.qml} | 0 .../RoomList}/ExploreComponent.qml | 0 src/qml/Page/RoomList/Page.qml | 301 +++++++++++ src/qml/Page/RoomList/RoomDelegate.qml | 136 +++++ src/qml/Page/RoomList/SpaceDelegate.qml | 47 ++ .../RoomList}/SpaceListContextMenu.qml | 0 src/qml/Page/RoomList/SpaceListView.qml | 64 +++ .../{Component => Page/RoomList}/UserInfo.qml | 0 src/qml/Page/RoomListPage.qml | 502 ------------------ src/qml/main.qml | 8 +- src/res.qrc | 21 +- 14 files changed, 624 insertions(+), 515 deletions(-) rename src/qml/{Menu => Page/RoomList}/AccountMenu.qml (95%) create mode 100644 src/qml/Page/RoomList/CollapsedRoomDelegate.qml rename src/qml/{Menu/RoomListContextMenu.qml => Page/RoomList/ContextMenu.qml} (100%) rename src/qml/{Component => Page/RoomList}/ExploreComponent.qml (100%) create mode 100644 src/qml/Page/RoomList/Page.qml create mode 100644 src/qml/Page/RoomList/RoomDelegate.qml create mode 100644 src/qml/Page/RoomList/SpaceDelegate.qml rename src/qml/{Menu => Page/RoomList}/SpaceListContextMenu.qml (100%) create mode 100644 src/qml/Page/RoomList/SpaceListView.qml rename src/qml/{Component => Page/RoomList}/UserInfo.qml (100%) delete mode 100644 src/qml/Page/RoomListPage.qml diff --git a/src/qml/Dialog/ConfirmLogoutDialog.qml b/src/qml/Dialog/ConfirmLogoutDialog.qml index e0e5c02a5..6b53cccd1 100644 --- a/src/qml/Dialog/ConfirmLogoutDialog.qml +++ b/src/qml/Dialog/ConfirmLogoutDialog.qml @@ -20,8 +20,8 @@ QQC2.Dialog { } } - x: Math.round((parent.width - width) / 2) - y: Math.round((parent.height - height) / 2) + x: parent ? Math.round((parent.width - width) / 2) : 0 + y: parent ? Math.round((parent.height - height) / 2) : 0 modal: true footer: QQC2.DialogButtonBox { diff --git a/src/qml/Menu/AccountMenu.qml b/src/qml/Page/RoomList/AccountMenu.qml similarity index 95% rename from src/qml/Menu/AccountMenu.qml rename to src/qml/Page/RoomList/AccountMenu.qml index a0218ea65..3e488980e 100644 --- a/src/qml/Menu/AccountMenu.qml +++ b/src/qml/Page/RoomList/AccountMenu.qml @@ -8,6 +8,7 @@ import QtQuick.Layouts 1.15 import org.kde.kirigami 2.19 as Kirigami import org.kde.neochat 1.0 +import '../Dialog' as Dialog QQC2.Menu { id: root @@ -38,7 +39,7 @@ QQC2.Menu { onTriggered: confirmLogoutDialog.open() } - ConfirmLogoutDialog { + Dialog.ConfirmLogoutDialog { id: confirmLogoutDialog } } diff --git a/src/qml/Page/RoomList/CollapsedRoomDelegate.qml b/src/qml/Page/RoomList/CollapsedRoomDelegate.qml new file mode 100644 index 000000000..d5a30208c --- /dev/null +++ b/src/qml/Page/RoomList/CollapsedRoomDelegate.qml @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2023 Carl Schwan +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import org.kde.kirigami 2.15 as Kirigami +import org.kde.kitemmodels 1.0 + +import org.kde.neochat 1.0 + +import './' as RoomList + +QQC2.ItemDelegate { + id: root + + required property var currentRoom + required property bool categoryVisible + required property string filterText + required property string avatar + required property string name + + topPadding: Kirigami.Units.largeSpacing + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + + width: ListView.view.width + height: visible ? ListView.view.width : 0 + + visible: root.categoryVisible || filterText.length > 0 || Config.mergeRoomList + + contentItem: Kirigami.Avatar { + source: root.avatar ? `image://mxc/${root.avatar}` : "" + name: root.name || i18n("No Name") + + sourceSize { + width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + } + } + + onClicked: RoomManager.enterRoom(root.currentRoom) + + Keys.onEnterPressed: RoomManager.enterRoom(root.currentRoom) + Keys.onReturnPressed: RoomManager.enterRoom(root.currentRoom) + + QQC2.ToolTip.visible: text.length > 0 && hovered + QQC2.ToolTip.text: root.name ?? "" + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay +} diff --git a/src/qml/Menu/RoomListContextMenu.qml b/src/qml/Page/RoomList/ContextMenu.qml similarity index 100% rename from src/qml/Menu/RoomListContextMenu.qml rename to src/qml/Page/RoomList/ContextMenu.qml diff --git a/src/qml/Component/ExploreComponent.qml b/src/qml/Page/RoomList/ExploreComponent.qml similarity index 100% rename from src/qml/Component/ExploreComponent.qml rename to src/qml/Page/RoomList/ExploreComponent.qml diff --git a/src/qml/Page/RoomList/Page.qml b/src/qml/Page/RoomList/Page.qml new file mode 100644 index 000000000..fe5b634fe --- /dev/null +++ b/src/qml/Page/RoomList/Page.qml @@ -0,0 +1,301 @@ +// SPDX-FileCopyrightText: 2019 Black Hat +// SPDX-FileCopyrightText: 2020 Carl Schwan +// SPDX-License-Identifier: GPL-3.0-only + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import org.kde.kirigami 2.15 as Kirigami +import org.kde.kitemmodels 1.0 + +import org.kde.neochat 1.0 + +import './' as RoomList + +Kirigami.ScrollablePage { + id: root + + /** + * @brief The current width of the room list. + * + * @note Other objects can access the value but the private function makes sure + * that only the internal members can modify it. + */ + readonly property int currentWidth: _private.currentWidth + + readonly property RoomListModel roomListModel: RoomListModel { + connection: Controller.activeConnection + } + + readonly property bool collapsed: Config.collapsed + + property var enteredRoom: null + + onCollapsedChanged: if (collapsed) { + sortFilterRoomListModel.filterText = ""; + } + + header: ColumnLayout { + visible: !root.collapsed + spacing: 0 + + RoomList.SpaceListView { + roomListModel: root.roomListModel + } + + Kirigami.Separator { + Layout.fillWidth: true + } + + Component { + id: spaceListContextMenu + SpaceListContextMenu {} + } + } + + Connections { + target: RoomManager + function onCurrentRoomChanged() { + itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent) + } + } + + function goToNextRoomFiltered(condition) { + let index = listView.currentIndex; + while (index++ !== listView.count - 1) { + if (condition(listView.itemAtIndex(index))) { + listView.currentIndex = index; + listView.currentItem.action.trigger(); + return; + } + } + } + + function goToPreviousRoomFiltered(condition) { + let index = listView.currentIndex; + while (index-- !== 0) { + if (condition(listView.itemAtIndex(index))) { + listView.currentIndex = index; + listView.currentItem.action.trigger(); + return; + } + } + } + + function goToNextRoom() { + goToNextRoomFiltered((item) => item.visible); + } + + function goToPreviousRoom() { + goToPreviousRoomFiltered((item) => item.visible); + } + + function goToNextUnreadRoom() { + goToNextRoomFiltered((item) => (item.visible && item.hasUnread)); + } + + function goToPreviousUnreadRoom() { + goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread)); + } + + titleDelegate: ExploreComponent { + Layout.fillWidth: true + desiredWidth: root.width - Kirigami.Units.largeSpacing + collapsed: root.collapsed + } + + ListView { + id: listView + + activeFocusOnTab: true + clip: AccountRegistry.count > 1 + + header: QQC2.ItemDelegate { + width: visible ? ListView.view.width : 0 + height: visible ? Kirigami.Units.gridUnit * 2 : 0 + + visible: root.collapsed + + topPadding: Kirigami.Units.largeSpacing + leftPadding: Kirigami.Units.largeSpacing + rightPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + + onClicked: quickView.item.open(); + + Kirigami.Icon { + anchors.centerIn: parent + width: Kirigami.Units.iconSizes.smallMedium + height: Kirigami.Units.iconSizes.smallMedium + source: "search" + } + + Kirigami.Separator { + width: parent.width + anchors.bottom: parent.bottom + } + } + + Layout.fillWidth: true + + Kirigami.PlaceholderMessage { + anchors.centerIn: parent + width: parent.width - (Kirigami.Units.largeSpacing * 4) + visible: listView.count == 0 + text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started") + helpfulAction: Kirigami.Action { + icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add" + text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms") + onTriggered: pageStack.layers.push("qrc:/JoinRoomPage.qml", { + connection: Controller.activeConnection, + keyword: sortFilterRoomListModel.filterText + }) + } + } + + ItemSelectionModel { + id: itemSelection + model: root.roomListModel + onCurrentChanged: listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row + } + + model: SortFilterRoomListModel { + id: sortFilterRoomListModel + + sourceModel: root.roomListModel + roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories + onLayoutChanged: { + listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row + } + } + + section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null + section.delegate: root.collapsed ? foldButton : sectionHeader + + Component { + id: sectionHeader + Kirigami.ListSectionHeader { + height: implicitHeight + label: roomListModel.categoryName(section) + action: Kirigami.Action { + onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) + } + contentItem.children: QQC2.ToolButton { + icon { + name: roomListModel.categoryVisible(section) ? "go-up" : "go-down" + width: Kirigami.Units.iconSizes.small + height: Kirigami.Units.iconSizes.small + } + + onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) + } + } + } + Component { + id: foldButton + Item { + width: ListView.view.width + height: visible ? width : 0 + QQC2.ToolButton { + id: button + anchors.centerIn: parent + + icon { + name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section) + width: Kirigami.Units.iconSizes.smallMedium + height: Kirigami.Units.iconSizes.smallMedium + } + + onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) + + QQC2.ToolTip.text: roomListModel.categoryName(section) + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + } + } + + reuseItems: true + currentIndex: -1 // we don't want any room highlighted by default + + delegate: root.collapsed ? collapsedModeListComponent : normalModeListComponent + + Component { + id: collapsedModeListComponent + + RoomList.CollapsedRoomDelegate { + filterText: sortFilterRoomListModel.filterText + } + } + + Component { + id: normalModeListComponent + + RoomList.RoomDelegate { + filterText: sortFilterRoomListModel.filterText + } + } + } + + footer: UserInfo { + width: parent.width + visible: !root.collapsed + } + + MouseArea { + anchors.top: parent.top + anchors.bottom: parent.bottom + parent: applicationWindow().overlay.parent + + x: root.currentWidth - width / 2 + width: Kirigami.Units.smallSpacing * 2 + z: root.z + 1 + enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35 + visible: enabled + cursorShape: Qt.SplitHCursor + + property int _lastX + + onPressed: mouse => { + _lastX = mouse.x; + } + onPositionChanged: mouse => { + if (_lastX == -1) { + return; + } + if (mouse.x > _lastX) { + // we moved to the right + if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) { + // Here we get back directly to a more wide mode. + _private.currentWidth = _private.collapseWidth; + Config.collapsed = false; + } else if (_private.currentWidth >= _private.collapseWidth) { + // Increase page width + _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX)); + } + } else if (mouse.x < _lastX) { + const tmpWidth = _private.currentWidth - (_lastX - mouse.x); + if (tmpWidth < _private.collapseWidth) { + _private.currentWidth = Qt.binding(() => _private.collapsedSize + (root.contentItem.QQC2.ScrollBar.vertical.visible ? root.contentItem.QQC2.ScrollBar.vertical.width : 0)); + Config.collapsed = true; + } else { + _private.currentWidth = tmpWidth; + } + } + } + } + + /* + * Hold the modifiable currentWidth in a private object so that only internal + * members can modify it. + */ + QtObject { + id: _private + property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth + readonly property int defaultWidth: Kirigami.Units.gridUnit * 17 + readonly property int collapseWidth: Kirigami.Units.gridUnit * 10 + readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3 + } +} diff --git a/src/qml/Page/RoomList/RoomDelegate.qml b/src/qml/Page/RoomList/RoomDelegate.qml new file mode 100644 index 000000000..5acaae08f --- /dev/null +++ b/src/qml/Page/RoomList/RoomDelegate.qml @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2023 Carl Schwan +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 +import QtQml.Models 2.15 + +import org.kde.kirigami 2.15 as Kirigami +import org.kde.kitemmodels 1.0 + +import org.kde.neochat 1.0 + +import './' as RoomList + +Kirigami.BasicListItem { + id: root + + required property int index + required property int unreadCount + required property int notificationCount + required property int highlightCount + required property var currentRoom + required property bool categoryVisible + required property string filterText + required property string avatar + required property string subtitleText + + required property string name + + readonly property bool hasUnread: unreadCount > 0 + + topPadding: Kirigami.Units.largeSpacing + bottomPadding: Kirigami.Units.largeSpacing + + visible: root.categoryVisible || root.filterText.length > 0 || Config.mergeRoomList + highlighted: ListView.view.currentIndex === index + focus: true + icon: undefined + bold: root.unreadCount > 0 + + label: root.name ?? "" + labelItem.textFormat: Text.PlainText + + subtitle: root.subtitleText + subtitleItem { + textFormat: Text.PlainText + visible: !Config.compactRoomList + } + + onClicked: RoomManager.enterRoom(root.currentRoom) + onPressAndHold: createRoomListContextMenu() + + Keys.onEnterPressed: RoomManager.enterRoom(root.currentRoom) + Keys.onReturnPressed: RoomManager.enterRoom(root.currentRoom) + + TapHandler { + acceptedButtons: Qt.RightButton + acceptedDevices: PointerDevice.Mouse + onTapped: createRoomListContextMenu() + } + + leading: Kirigami.Avatar { + source: root.avatar ? `image://mxc/${root.avatar}` : "" + name: root.name || i18n("No Name") + implicitWidth: visible ? height : 0 + visible: Config.showAvatarInRoomDrawer + sourceSize { + width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 + } + } + + trailing: RowLayout { + Kirigami.Icon { + source: "notifications-disabled" + enabled: false + implicitWidth: Kirigami.Units.iconSizes.smallMedium + implicitHeight: Kirigami.Units.iconSizes.smallMedium + visible: currentRoom.pushNotificationState === PushNotificationState.Mute && !configButton.visible && unreadCount <= 0 + Accessible.name: i18n("Muted room") + Layout.rightMargin: Kirigami.Units.smallSpacing + } + QQC2.Label { + id: notificationCountLabel + text: notificationCount > 0 ? notificationCount : "●" + visible: unreadCount > 0 + color: Kirigami.Theme.textColor + horizontalAlignment: Text.AlignHCenter + background: Rectangle { + visible: notificationCount > 0 + Kirigami.Theme.colorSet: Kirigami.Theme.Button + color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor + opacity: highlightCount > 0 ? 1 : 0.3 + radius: height / 2 + } + + Layout.rightMargin: Kirigami.Units.smallSpacing + Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium + Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) + + TextMetrics { + id: notificationCountTextMetrics + text: notificationCountLabel.text + } + } + QQC2.Button { + id: configButton + visible: root.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList + text: i18n("Configure room") + display: QQC2.Button.IconOnly + + icon.name: "configure" + onClicked: createRoomListContextMenu() + } + } + + function createRoomListContextMenu() { + const component = Qt.createComponent(Qt.resolvedUrl("./ContextMenu.qml")) + const menu = component.createObject(root, { + room: root.currentRoom, + }); + if (!Kirigami.Settings.isMobile && !Config.compactRoomList) { + configButton.visible = true; + configButton.down = true; + } + menu.closed.connect(function() { + configButton.down = undefined; + configButton.visible = Qt.binding(() => { + return root.hovered && !Kirigami.Settings.isMobile + && !Config.compactRoomList; + }); + }) + menu.open() + } +} diff --git a/src/qml/Page/RoomList/SpaceDelegate.qml b/src/qml/Page/RoomList/SpaceDelegate.qml new file mode 100644 index 000000000..42a673cb4 --- /dev/null +++ b/src/qml/Page/RoomList/SpaceDelegate.qml @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2022 Snehit Sah +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.neochat 1.0 + +QQC2.ItemDelegate { + id: root + + required property string avatar + required property var currentRoom + required property int index + required property string id + + signal createContextMenu(currentRoom: var) + signal spaceSelected(spaceId: string) + + height: ListView.view.height + width: height + + leftPadding: topPadding + rightPadding: topPadding + + contentItem: Kirigami.Avatar { + name: currentRoom.displayName + source: avatar !== "" ? "image://mxc/" + avatar : "" + } + + onClicked: root.spaceSelected(id) + onPressAndHold: root.createContextMenu(root.currentRoom) + + Accessible.name: currentRoom.displayName + + QQC2.ToolTip.text: currentRoom.displayName + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + + TapHandler { + acceptedButtons: Qt.RightButton + acceptedDevices: PointerDevice.Mouse + onTapped: root.createContextMenu(root.currentRoom) + } +} diff --git a/src/qml/Menu/SpaceListContextMenu.qml b/src/qml/Page/RoomList/SpaceListContextMenu.qml similarity index 100% rename from src/qml/Menu/SpaceListContextMenu.qml rename to src/qml/Page/RoomList/SpaceListContextMenu.qml diff --git a/src/qml/Page/RoomList/SpaceListView.qml b/src/qml/Page/RoomList/SpaceListView.qml new file mode 100644 index 000000000..6c3ae5b02 --- /dev/null +++ b/src/qml/Page/RoomList/SpaceListView.qml @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2022 Snehit Sah +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.20 as Kirigami +import org.kde.neochat 1.0 + +ListView { + id: root + + required property RoomListModel roomListModel + + orientation: Qt.Horizontal + spacing: Kirigami.Units.smallSpacing + clip: true + visible: root.count > 0 + + model: SortFilterSpaceListModel { + id: sortFilterSpaceListModel + sourceModel: root.roomListModel + } + + header: QQC2.ItemDelegate { + id: homeButton + icon.name: "home" + text: i18nc("@action:button", "Show All Rooms") + height: parent.height + width: height + leftPadding: topPadding + rightPadding: topPadding + + contentItem: Kirigami.Icon { + source: "home" + } + + onClicked: { + sortFilterRoomListModel.activeSpaceId = ""; + listView.positionViewAtIndex(0, ListView.Beginning); + } + + QQC2.ToolTip.text: homeButton.text + QQC2.ToolTip.visible: hovered + QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay + } + + delegate: SpaceDelegate { + onSpaceSelected: (spaceId) => { + sortFilterRoomListModel.activeSpaceId = spaceId; + } + + onCreateContextMenu: () => { + const menu = spaceListContextMenu.createObject(page, { + room: currentRoom, + }); + menu.open(); + } + } + + Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + Layout.fillWidth: true +} diff --git a/src/qml/Component/UserInfo.qml b/src/qml/Page/RoomList/UserInfo.qml similarity index 100% rename from src/qml/Component/UserInfo.qml rename to src/qml/Page/RoomList/UserInfo.qml diff --git a/src/qml/Page/RoomListPage.qml b/src/qml/Page/RoomListPage.qml deleted file mode 100644 index 4014cc97f..000000000 --- a/src/qml/Page/RoomListPage.qml +++ /dev/null @@ -1,502 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Black Hat -// SPDX-FileCopyrightText: 2020 Carl Schwan -// SPDX-License-Identifier: GPL-3.0-only - -import QtQuick 2.15 -import QtQuick.Controls 2.15 as QQC2 -import QtQuick.Layouts 1.15 -import QtQml.Models 2.15 - -import org.kde.kirigami 2.15 as Kirigami -import org.kde.kitemmodels 1.0 - -import org.kde.neochat 1.0 - -Kirigami.ScrollablePage { - id: page - - /** - * @brief The current width of the room list. - * - * @note Other objects can access the value but the private function makes sure - * that only the internal members can modify it. - */ - readonly property int currentWidth: _private.currentWidth - - readonly property bool collapsed: Config.collapsed - onCollapsedChanged: if (collapsed) { - sortFilterRoomListModel.filterText = ""; - } - - header: ColumnLayout { - visible: !page.collapsed - spacing: 0 - - ListView { - id: spaceList - property string activeSpaceId: "" - - orientation: Qt.Horizontal - spacing: Kirigami.Units.smallSpacing - clip: true - visible: spaceList.count > 0 - - Layout.preferredHeight: Kirigami.Units.gridUnit * 2 - Layout.fillWidth: true - - model: SortFilterSpaceListModel { - id: sortFilterSpaceListModel - sourceModel: roomListModel - } - - header: QQC2.ItemDelegate { - id: homeButton - icon.name: "home" - text: i18nc("@action:button", "Show All Rooms") - height: parent.height - width: height - leftPadding: topPadding - rightPadding: topPadding - - contentItem: Kirigami.Icon { - source: "home" - } - - onClicked: { - sortFilterRoomListModel.activeSpaceId = ""; - spaceList.activeSpaceId = ''; - listView.positionViewAtIndex(0, ListView.Beginning); - } - - QQC2.ToolTip.text: homeButton.text - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - - delegate: QQC2.ItemDelegate { - required property string avatar - required property var currentRoom - required property int index - required property string id - - height: parent.height - width: height - leftPadding: topPadding - rightPadding: topPadding - - contentItem: Kirigami.Avatar { - name: currentRoom.displayName - source: avatar !== "" ? "image://mxc/" + avatar : "" - } - - onClicked: { - spaceList.activeSpaceId = id; - sortFilterRoomListModel.activeSpaceId = id; - } - - Accessible.name: currentRoom.displayName - - QQC2.ToolTip.text: currentRoom.displayName - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - - onPressAndHold: { - spaceList.createContextMenu(currentRoom) - } - TapHandler { - acceptedButtons: Qt.RightButton - acceptedDevices: PointerDevice.Mouse - onTapped: spaceList.createContextMenu(currentRoom) - } - } - function createContextMenu(room) { - const menu = spaceListContextMenu.createObject(page, {room: room}) - menu.open() - } - } - - Kirigami.Separator { - Layout.fillWidth: true - } - - Component { - id: spaceListContextMenu - SpaceListContextMenu {} - } - } - - property var enteredRoom - - Connections { - target: RoomManager - function onCurrentRoomChanged() { - itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent) - } - } - - function goToNextRoomFiltered(condition) { - let index = listView.currentIndex; - while (index++ !== listView.count - 1) { - if (condition(listView.itemAtIndex(index))) { - listView.currentIndex = index; - listView.currentItem.action.trigger(); - return; - } - } - } - - function goToPreviousRoomFiltered(condition) { - let index = listView.currentIndex; - while (index-- !== 0) { - if (condition(listView.itemAtIndex(index))) { - listView.currentIndex = index; - listView.currentItem.action.trigger(); - return; - } - } - } - - function goToNextRoom() { - goToNextRoomFiltered((item) => item.visible); - } - - function goToPreviousRoom() { - goToPreviousRoomFiltered((item) => item.visible); - } - - function goToNextUnreadRoom() { - goToNextRoomFiltered((item) => (item.visible && item.hasUnread)); - } - - function goToPreviousUnreadRoom() { - goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread)); - } - - titleDelegate: ExploreComponent { - Layout.fillWidth: true - desiredWidth: page.width - Kirigami.Units.largeSpacing - collapsed: page.collapsed - } - - ListView { - id: listView - - activeFocusOnTab: true - clip: AccountRegistry.count > 1 - - header: QQC2.ItemDelegate { - visible: page.collapsed - action: Kirigami.Action { - id: enterRoomAction - onTriggered: quickView.item.open(); - } - topPadding: Kirigami.Units.largeSpacing - leftPadding: Kirigami.Units.largeSpacing - rightPadding: Kirigami.Units.largeSpacing - bottomPadding: Kirigami.Units.largeSpacing - width: visible ? ListView.view.width : 0 - height: visible ? Kirigami.Units.gridUnit * 2 : 0 - - Kirigami.Icon { - anchors.centerIn: parent - width: 22 - height: 22 - source: "search" - } - Kirigami.Separator { - width: parent.width - anchors.bottom: parent.bottom - } - } - - Layout.fillWidth: true - - Kirigami.PlaceholderMessage { - anchors.centerIn: parent - width: parent.width - (Kirigami.Units.largeSpacing * 4) - visible: listView.count == 0 - text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started") - helpfulAction: Kirigami.Action { - icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add" - text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms") - onTriggered: pageStack.layers.push("qrc:/JoinRoomPage.qml", { - connection: Controller.activeConnection, - keyword: sortFilterRoomListModel.filterText - }) - } - } - - - ItemSelectionModel { - id: itemSelection - model: roomListModel - onCurrentChanged: { - listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row - } - } - - model: SortFilterRoomListModel { - id: sortFilterRoomListModel - sourceModel: RoomListModel { - id: roomListModel - connection: Controller.activeConnection - } - roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories - onLayoutChanged: { - listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row - } - } - - section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null - section.delegate: page.collapsed ? foldButton : sectionHeader - - Component { - id: sectionHeader - Kirigami.ListSectionHeader { - height: implicitHeight - label: roomListModel.categoryName(section) - action: Kirigami.Action { - onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) - } - contentItem.children: QQC2.ToolButton { - icon.name: (roomListModel.categoryVisible(section) ? "go-up" : "go-down") - icon.width: Kirigami.Units.iconSizes.small - icon.height: Kirigami.Units.iconSizes.small - - onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) - } - } - } - Component { - id: foldButton - Item { - width: ListView.view.width - height: visible ? width : 0 - QQC2.ToolButton { - id: button - anchors.centerIn: parent - - icon.name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section) - icon.width: Kirigami.Units.iconSizes.smallMedium - icon.height: Kirigami.Units.iconSizes.smallMedium - - onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section)) - - QQC2.ToolTip.text: roomListModel.categoryName(section) - QQC2.ToolTip.visible: hovered - QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay - } - } - } - - reuseItems: true - currentIndex: -1 // we don't want any room highlighted by default - - delegate: page.collapsed ? collapsedModeListComponent : normalModeListComponent - - Component { - id: collapsedModeListComponent - - QQC2.ItemDelegate { - action: Kirigami.Action { - id: enterRoomAction - onTriggered: { - RoomManager.enterRoom(currentRoom); - } - } - Keys.onEnterPressed: enterRoomAction.trigger() - Keys.onReturnPressed: enterRoomAction.trigger() - topPadding: Kirigami.Units.largeSpacing - leftPadding: Kirigami.Units.largeSpacing - rightPadding: Kirigami.Units.largeSpacing - bottomPadding: Kirigami.Units.largeSpacing - width: ListView.view.width - height: visible ? ListView.view.width : 0 - visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList - - contentItem: Kirigami.Avatar { - source: avatar ? "image://mxc/" + avatar : "" - name: model.name || i18n("No Name") - sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - } - - QQC2.ToolTip { - enabled: text.length !== 0 - text: name ?? "" - } - } - } - - Component { - id: roomListContextMenu - RoomListContextMenu {} - } - - Component { - id: normalModeListComponent - Kirigami.BasicListItem { - id: roomListItem - visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList - topPadding: Kirigami.Units.largeSpacing - bottomPadding: Kirigami.Units.largeSpacing - highlighted: listView.currentIndex === index - focus: true - icon: undefined - action: Kirigami.Action { - id: enterRoomAction - onTriggered: { - RoomManager.enterRoom(currentRoom); - } - } - Keys.onEnterPressed: enterRoomAction.trigger() - Keys.onReturnPressed: enterRoomAction.trigger() - bold: unreadCount > 0 - label: name ?? "" - labelItem.textFormat: Text.PlainText - subtitle: subtitleText - subtitleItem.textFormat: Text.PlainText - subtitleItem.visible: !Config.compactRoomList - onPressAndHold: { - createRoomListContextMenu() - } - TapHandler { - acceptedButtons: Qt.RightButton - acceptedDevices: PointerDevice.Mouse - onTapped: createRoomListContextMenu() - } - - leading: Kirigami.Avatar { - source: avatar ? "image://mxc/" + avatar : "" - name: model.name || i18n("No Name") - implicitWidth: visible ? height : 0 - visible: Config.showAvatarInRoomDrawer - sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2 - } - - trailing: RowLayout { - Kirigami.Icon { - source: "notifications-disabled" - enabled: false - implicitWidth: Kirigami.Units.iconSizes.smallMedium - implicitHeight: Kirigami.Units.iconSizes.smallMedium - Layout.rightMargin: Kirigami.Units.smallSpacing - visible: currentRoom.pushNotificationState === PushNotificationState.Mute && !configButton.visible && unreadCount <= 0 - Accessible.name: i18n("Muted room") - } - QQC2.Label { - id: notificationCountLabel - text: notificationCount > 0 ? notificationCount : "●" - visible: unreadCount > 0 - color: Kirigami.Theme.textColor - Layout.rightMargin: Kirigami.Units.smallSpacing - Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium - Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height) - horizontalAlignment: Text.AlignHCenter - background: Rectangle { - visible: notificationCount > 0 - Kirigami.Theme.colorSet: Kirigami.Theme.Button - color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor - opacity: highlightCount > 0 ? 1 : 0.3 - radius: height / 2 - } - - TextMetrics { - id: notificationCountTextMetrics - text: notificationCountLabel.text - } - } - QQC2.Button { - id: configButton - visible: roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList - Accessible.name: i18n("Configure room") - - action: Kirigami.Action { - id: optionAction - icon.name: "configure" - onTriggered: { - createRoomListContextMenu() - } - } - } - } - - function createRoomListContextMenu() { - const menu = roomListContextMenu.createObject(page, {room: currentRoom}) - if (!Kirigami.Settings.isMobile && !Config.compactRoomList) { - configButton.visible = true - configButton.down = true - } - menu.closed.connect(function() { - configButton.down = undefined - configButton.visible = Qt.binding(function() { return roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList }) - }) - menu.open() - } - - readonly property bool hasUnread: unreadCount > 0 - } - } - } - - footer: UserInfo { - width: parent.width - visible: !page.collapsed - } - - MouseArea { - anchors.top: parent.top - anchors.bottom: parent.bottom - parent: applicationWindow().overlay.parent - - x: page.currentWidth - width / 2 - width: Kirigami.Units.smallSpacing * 2 - z: page.z + 1 - enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35 - visible: enabled - cursorShape: Qt.SplitHCursor - - property int _lastX - - onPressed: mouse => { - _lastX = mouse.x; - } - onPositionChanged: mouse => { - if (_lastX == -1) { - return; - } - if (mouse.x > _lastX) { - // we moved to the right - if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) { - // Here we get back directly to a more wide mode. - _private.currentWidth = _private.collapseWidth; - Config.collapsed = false; - } else if (_private.currentWidth >= _private.collapseWidth) { - // Increase page width - _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX)); - } - } else if (mouse.x < _lastX) { - const tmpWidth = _private.currentWidth - (_lastX - mouse.x); - if (tmpWidth < _private.collapseWidth) { - _private.currentWidth = Qt.binding(() => _private.collapsedSize + (page.contentItem.QQC2.ScrollBar.vertical.visible ? page.contentItem.QQC2.ScrollBar.vertical.width : 0)); - Config.collapsed = true; - } else { - _private.currentWidth = tmpWidth; - } - } - } - } - - /* - * Hold the modifiable currentWidth in a private object so that only internal - * members can modify it. - */ - QtObject { - id: _private - property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth - readonly property int defaultWidth: Kirigami.Units.gridUnit * 17 - readonly property int collapseWidth: Kirigami.Units.gridUnit * 10 - readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3 - } -} diff --git a/src/qml/main.qml b/src/qml/main.qml index 8e8b137b6..aa23fab1e 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -9,6 +9,8 @@ import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami import org.kde.neochat 1.0 +import './RoomList' as RoomList +import './Dialog' as Dialog Kirigami.ApplicationWindow { id: root @@ -24,7 +26,7 @@ Kirigami.ApplicationWindow { pageStack.initialPage: LoadingPage {} pageStack.globalToolBar.canContainHandles: true - property RoomListPage roomListPage + property RoomList.Page roomListPage property bool roomListLoaded: false property RoomPage roomPage @@ -167,7 +169,7 @@ Kirigami.ApplicationWindow { pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 || pageStack.layers.depth > 1 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : 0 - ConfirmLogoutDialog { + Dialog.ConfirmLogoutDialog { id: confirmLogoutDialog } @@ -200,7 +202,7 @@ Kirigami.ApplicationWindow { Component { id: roomListComponent - RoomListPage { + RoomList.Page { id: roomList Shortcut { diff --git a/src/res.qrc b/src/res.qrc index 4cc2b8f78..283bd1b65 100644 --- a/src/res.qrc +++ b/src/res.qrc @@ -2,9 +2,21 @@ ../org.kde.neochat.svg ../org.kde.neochat.tray.svg + qml/main.qml + + qml/Page/RoomList/AccountMenu.qml + qml/Page/RoomList/ExploreComponent.qml + qml/Page/RoomList/ContextMenu.qml + qml/Page/RoomList/CollapsedRoomDelegate.qml + qml/Page/RoomList/RoomDelegate.qml + qml/Page/RoomList/Page.qml + qml/Page/RoomList/SpaceListContextMenu.qml + qml/Page/RoomList/SpaceDelegate.qml + qml/Page/RoomList/SpaceListView.qml + qml/Page/RoomList/UserInfo.qml + qml/Page/LoadingPage.qml - qml/Page/RoomListPage.qml qml/Page/RoomPage.qml qml/Page/RoomWindow.qml qml/Page/JoinRoomPage.qml @@ -18,12 +30,10 @@ qml/RoomSettings/Categories.qml qml/RoomSettings/Permissions.qml qml/Component/FullScreenImage.qml - qml/Component/UserInfo.qml qml/Component/FancyEffectsContainer.qml qml/Component/TypingPane.qml qml/Component/ShimmerGradient.qml qml/Component/QuickSwitcher.qml - qml/Component/ExploreComponent.qml qml/Component/ChatBox/ChatBox.qml qml/Component/ChatBox/ChatBar.qml qml/Component/ChatBox/AttachmentPane.qml @@ -66,7 +76,7 @@ qml/Dialog/EmojiDialog.qml qml/Dialog/OpenFileDialog.qml qml/Dialog/KeyVerification/KeyVerificationDialog.qml - qml/Dialog/ConfirmLogoutDialog.qml + qml/Dialog/ConfirmLogoutDialog.qml qml/Dialog/PowerLevelDialog.qml qml/Dialog/KeyVerification/Message.qml qml/Dialog/KeyVerification/EmojiItem.qml @@ -75,13 +85,10 @@ qml/Dialog/KeyVerification/VerificationCanceled.qml qml/Menu/GlobalMenu.qml qml/Menu/EditMenu.qml - qml/Menu/AccountMenu.qml qml/Menu/Timeline/MessageDelegateContextMenu.qml qml/Menu/Timeline/FileDelegateContextMenu.qml qml/Menu/Timeline/MessageSourceSheet.qml qml/Menu/Timeline/ReportSheet.qml - qml/Menu/RoomListContextMenu.qml - qml/Menu/SpaceListContextMenu.qml qml/Component/glowdot.png qml/Component/confetti.png qml/Settings/SettingsPage.qml