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 <carl@carlschwan.eu>
This commit is contained in:
Carl Schwan
2023-03-31 16:36:15 +02:00
parent a67f3334ea
commit 3ccff4f337
14 changed files with 624 additions and 515 deletions

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
// 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
}

View File

@@ -0,0 +1,301 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// 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
}
}

View File

@@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
// 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()
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <snehitsah@protonmail.com>
// 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)
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <snehitsah@protonmail.com>
// 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
}

View File

@@ -1,502 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// 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
}
}

View File

@@ -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 {

View File

@@ -2,9 +2,21 @@
<qresource prefix="/">
<file alias="icons/org.kde.neochat.svg">../org.kde.neochat.svg</file>
<file alias="icons/org.kde.neochat.tray.svg">../org.kde.neochat.tray.svg</file>
<file alias="main.qml">qml/main.qml</file>
<file alias="RoomList/AccountMenu.qml">qml/Page/RoomList/AccountMenu.qml</file>
<file alias="RoomList/ExploreComponent.qml">qml/Page/RoomList/ExploreComponent.qml</file>
<file alias="RoomList/ContextMenu.qml">qml/Page/RoomList/ContextMenu.qml</file>
<file alias="RoomList/CollapsedRoomDelegate.qml">qml/Page/RoomList/CollapsedRoomDelegate.qml</file>
<file alias="RoomList/RoomDelegate.qml">qml/Page/RoomList/RoomDelegate.qml</file>
<file alias="RoomList/Page.qml">qml/Page/RoomList/Page.qml</file>
<file alias="RoomList/SpaceListContextMenu.qml">qml/Page/RoomList/SpaceListContextMenu.qml</file>
<file alias="RoomList/SpaceDelegate.qml">qml/Page/RoomList/SpaceDelegate.qml</file>
<file alias="RoomList/SpaceListView.qml">qml/Page/RoomList/SpaceListView.qml</file>
<file alias="RoomList/UserInfo.qml">qml/Page/RoomList/UserInfo.qml</file>
<file alias="LoadingPage.qml">qml/Page/LoadingPage.qml</file>
<file alias="RoomListPage.qml">qml/Page/RoomListPage.qml</file>
<file alias="RoomPage.qml">qml/Page/RoomPage.qml</file>
<file alias="RoomWindow.qml">qml/Page/RoomWindow.qml</file>
<file alias="JoinRoomPage.qml">qml/Page/JoinRoomPage.qml</file>
@@ -18,12 +30,10 @@
<file alias="Categories.qml">qml/RoomSettings/Categories.qml</file>
<file alias="Permissions.qml">qml/RoomSettings/Permissions.qml</file>
<file alias="FullScreenImage.qml">qml/Component/FullScreenImage.qml</file>
<file alias="UserInfo.qml">qml/Component/UserInfo.qml</file>
<file alias="FancyEffectsContainer.qml">qml/Component/FancyEffectsContainer.qml</file>
<file alias="TypingPane.qml">qml/Component/TypingPane.qml</file>
<file alias="ShimmerGradient.qml">qml/Component/ShimmerGradient.qml</file>
<file alias="QuickSwitcher.qml">qml/Component/QuickSwitcher.qml</file>
<file alias="ExploreComponent.qml">qml/Component/ExploreComponent.qml</file>
<file alias="ChatBox.qml">qml/Component/ChatBox/ChatBox.qml</file>
<file alias="ChatBar.qml">qml/Component/ChatBox/ChatBar.qml</file>
<file alias="AttachmentPane.qml">qml/Component/ChatBox/AttachmentPane.qml</file>
@@ -66,7 +76,7 @@
<file alias="EmojiDialog.qml">qml/Dialog/EmojiDialog.qml</file>
<file alias="OpenFileDialog.qml">qml/Dialog/OpenFileDialog.qml</file>
<file alias="KeyVerificationDialog.qml">qml/Dialog/KeyVerification/KeyVerificationDialog.qml</file>
<file alias="ConfirmLogoutDialog.qml">qml/Dialog/ConfirmLogoutDialog.qml</file>
<file alias="Dialog/ConfirmLogoutDialog.qml">qml/Dialog/ConfirmLogoutDialog.qml</file>
<file alias="PowerLevelDialog.qml">qml/Dialog/PowerLevelDialog.qml</file>
<file alias="Message.qml">qml/Dialog/KeyVerification/Message.qml</file>
<file alias="EmojiItem.qml">qml/Dialog/KeyVerification/EmojiItem.qml</file>
@@ -75,13 +85,10 @@
<file alias="VerificationCanceled.qml">qml/Dialog/KeyVerification/VerificationCanceled.qml</file>
<file alias="GlobalMenu.qml">qml/Menu/GlobalMenu.qml</file>
<file alias="EditMenu.qml">qml/Menu/EditMenu.qml</file>
<file alias="AccountMenu.qml">qml/Menu/AccountMenu.qml</file>
<file alias="MessageDelegateContextMenu.qml">qml/Menu/Timeline/MessageDelegateContextMenu.qml</file>
<file alias="FileDelegateContextMenu.qml">qml/Menu/Timeline/FileDelegateContextMenu.qml</file>
<file alias="MessageSourceSheet.qml">qml/Menu/Timeline/MessageSourceSheet.qml</file>
<file alias="ReportSheet.qml">qml/Menu/Timeline/ReportSheet.qml</file>
<file alias="RoomListContextMenu.qml">qml/Menu/RoomListContextMenu.qml</file>
<file alias="SpaceListContextMenu.qml">qml/Menu/SpaceListContextMenu.qml</file>
<file alias="glowdot.png">qml/Component/glowdot.png</file>
<file alias="confetti.png">qml/Component/confetti.png</file>
<file alias="SettingsPage.qml">qml/Settings/SettingsPage.qml</file>