Warning, /network/neochat/src/qml/RoomListPage.qml is written in an unsupported language. File is not indexed.
0001 // SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
0002 // SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
0003 // SPDX-License-Identifier: GPL-3.0-only
0004
0005 import QtQuick
0006 import QtQuick.Controls as QQC2
0007 import QtQuick.Layouts
0008 import QtQml.Models
0009
0010 import org.kde.kirigami as Kirigami
0011 import org.kde.kirigamiaddons.components as KirigamiComponents
0012 import org.kde.kirigamiaddons.delegates as Delegates
0013
0014 import org.kde.neochat
0015 import org.kde.neochat.config
0016 import org.kde.neochat.accounts
0017
0018 Kirigami.Page {
0019 id: root
0020
0021 /**
0022 * @brief The current width of the room list.
0023 *
0024 * @note Other objects can access the value but the private function makes sure
0025 * that only the internal members can modify it.
0026 */
0027 readonly property int currentWidth: _private.currentWidth + spaceListWidth
0028 readonly property alias spaceListWidth: spaceDrawer.width
0029
0030 required property NeoChatConnection connection
0031
0032 readonly property RoomListModel roomListModel: RoomListModel {
0033 connection: root.connection
0034 }
0035 property bool spaceChanging: false
0036
0037 readonly property bool collapsed: Config.collapsed
0038
0039 property var enteredRoom: null
0040
0041 onCollapsedChanged: if (collapsed) {
0042 sortFilterRoomListModel.filterText = "";
0043 }
0044
0045 Component.onCompleted: Runner.roomListModel = root.roomListModel
0046
0047 Connections {
0048 target: RoomManager
0049 function onCurrentRoomChanged() {
0050 itemSelection.setCurrentIndex(roomListModel.index(roomListModel.rowForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent);
0051 }
0052 }
0053
0054 function goToNextRoomFiltered(condition) {
0055 let index = listView.currentIndex;
0056 while (index++ !== listView.count - 1) {
0057 if (condition(listView.itemAtIndex(index))) {
0058 listView.currentIndex = index;
0059 listView.currentItem.clicked();
0060 return;
0061 }
0062 }
0063 }
0064
0065 function goToPreviousRoomFiltered(condition) {
0066 let index = listView.currentIndex;
0067 while (index-- !== 0) {
0068 if (condition(listView.itemAtIndex(index))) {
0069 listView.currentIndex = index;
0070 listView.currentItem.clicked();
0071 return;
0072 }
0073 }
0074 }
0075
0076 function goToNextRoom() {
0077 goToNextRoomFiltered(item => item.visible);
0078 }
0079
0080 function goToPreviousRoom() {
0081 goToPreviousRoomFiltered(item => item.visible);
0082 }
0083
0084 function goToNextUnreadRoom() {
0085 goToNextRoomFiltered(item => (item.visible && item.hasUnread));
0086 }
0087
0088 function goToPreviousUnreadRoom() {
0089 goToPreviousRoomFiltered(item => (item.visible && item.hasUnread));
0090 }
0091
0092 titleDelegate: Loader {
0093 Layout.fillWidth: true
0094 sourceComponent: Kirigami.Settings.isMobile ? userInfo : exploreComponent
0095 }
0096
0097 padding: 0
0098
0099 RowLayout {
0100 anchors.fill: parent
0101 spacing: 0
0102
0103 SpaceDrawer {
0104 id: spaceDrawer
0105 Layout.preferredWidth: spaceDrawer.enabled ? Kirigami.Units.gridUnit * 3 : 0
0106 Layout.fillHeight: true
0107
0108 connection: root.connection
0109
0110 onSelectionChanged: root.spaceChanging = true
0111 onSpacesUpdated: sortFilterRoomListModel.invalidate()
0112 }
0113
0114 Kirigami.Separator {
0115 Layout.fillHeight: true
0116 Layout.preferredWidth: 1
0117 }
0118
0119 QQC2.ScrollView {
0120 id: scrollView
0121 Layout.fillWidth: true
0122 Layout.fillHeight: true
0123
0124 background: Rectangle {
0125 color: Kirigami.Theme.backgroundColor
0126 Kirigami.Theme.colorSet: Kirigami.Theme.View
0127 }
0128
0129 ListView {
0130 id: listView
0131
0132 activeFocusOnTab: true
0133 clip: true
0134
0135 topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
0136
0137 header: QQC2.ItemDelegate {
0138 width: visible ? ListView.view.width : 0
0139 height: visible ? Kirigami.Units.gridUnit * 2 : 0
0140
0141 visible: root.collapsed
0142
0143 topPadding: Kirigami.Units.largeSpacing
0144 leftPadding: Kirigami.Units.largeSpacing
0145 rightPadding: Kirigami.Units.largeSpacing
0146 bottomPadding: Kirigami.Units.largeSpacing
0147
0148 onClicked: quickView.item.open()
0149
0150 Kirigami.Icon {
0151 anchors.centerIn: parent
0152 width: Kirigami.Units.iconSizes.smallMedium
0153 height: Kirigami.Units.iconSizes.smallMedium
0154 source: "search"
0155 }
0156
0157 Kirigami.Separator {
0158 width: parent.width
0159 anchors.bottom: parent.bottom
0160 }
0161 }
0162
0163 Kirigami.PlaceholderMessage {
0164 anchors.centerIn: parent
0165 width: parent.width - (Kirigami.Units.largeSpacing * 4)
0166 visible: listView.count == 0
0167 text: if (sortFilterRoomListModel.filterText.length > 0) {
0168 return spaceDrawer.showDirectChats ? i18n("No friends found") : i18n("No rooms found");
0169 } else {
0170 return spaceDrawer.showDirectChats ? i18n("You haven't added any of your friends yet, click below to search for them.") : i18n("Join some rooms to get started");
0171 }
0172 helpfulAction: spaceDrawer.showDirectChats ? userSearchAction : exploreRoomAction
0173
0174 Kirigami.Action {
0175 id: exploreRoomAction
0176 icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
0177 text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
0178 onTriggered: {
0179 let dialog = pageStack.layers.push("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {
0180 connection: root.connection,
0181 keyword: sortFilterRoomListModel.filterText
0182 }, {
0183 title: i18nc("@title", "Explore Rooms")
0184 });
0185 dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
0186 RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
0187 });
0188 }
0189 }
0190
0191 Kirigami.Action {
0192 id: userSearchAction
0193 icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
0194 text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in friend directory") : i18n("Find your friends")
0195 onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {
0196 connection: root.connection
0197 }, {
0198 title: i18nc("@title", "Find your friends")
0199 })
0200 }
0201 }
0202
0203 ItemSelectionModel {
0204 id: itemSelection
0205 model: root.roomListModel
0206 onCurrentChanged: (current, previous) => listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
0207 }
0208
0209 model: SortFilterRoomListModel {
0210 id: sortFilterRoomListModel
0211
0212 sourceModel: root.roomListModel
0213 roomSortOrder: SortFilterRoomListModel.Categories
0214 onLayoutChanged: {
0215 layoutTimer.restart();
0216 listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row;
0217 }
0218 activeSpaceId: spaceDrawer.selectedSpaceId
0219 mode: spaceDrawer.showDirectChats ? SortFilterRoomListModel.DirectChats : SortFilterRoomListModel.Rooms
0220 }
0221
0222 // HACK: This is the only way to guarantee the correct choice when
0223 // there are multiple property changes that invalidate the filter. I.e.
0224 // in this case activeSpaceId followed by mode.
0225 Timer {
0226 id: layoutTimer
0227 interval: 300
0228 onTriggered: if ((spaceDrawer.showDirectChats || spaceDrawer.selectedSpaceId.length < 1) && root.spaceChanging) {
0229 RoomManager.resolveResource(listView.itemAtIndex(0).currentRoom.id);
0230 root.spaceChanging = false;
0231 }
0232 }
0233
0234 section {
0235 property: "category"
0236 delegate: root.collapsed ? foldButton : sectionHeader
0237 }
0238
0239 Component {
0240 id: sectionHeader
0241 Kirigami.ListSectionHeader {
0242 height: implicitHeight
0243 width: listView.width
0244 label: roomListModel.categoryName(section)
0245 action: Kirigami.Action {
0246 onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
0247 }
0248
0249 QQC2.ToolButton {
0250 icon {
0251 name: roomListModel.categoryVisible(section) ? "go-up" : "go-down"
0252 width: Kirigami.Units.iconSizes.small
0253 height: Kirigami.Units.iconSizes.small
0254 }
0255 text: roomListModel.categoryVisible(section) ? i18nc("Collapse <section name>", "Collapse %1", roomListModel.categoryName(section)) : i18nc("Expand <section name", "Expand %1", roomListModel.categoryName(section))
0256 display: QQC2.Button.IconOnly
0257
0258 QQC2.ToolTip.text: text
0259 QQC2.ToolTip.visible: hovered
0260 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
0261
0262 onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
0263 }
0264 }
0265 }
0266 Component {
0267 id: foldButton
0268 Item {
0269 width: ListView.view.width
0270 height: visible ? width : 0
0271 QQC2.ToolButton {
0272 id: button
0273 anchors.centerIn: parent
0274
0275 icon {
0276 name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section)
0277 width: Kirigami.Units.iconSizes.smallMedium
0278 height: Kirigami.Units.iconSizes.smallMedium
0279 }
0280
0281 onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
0282
0283 QQC2.ToolTip.text: roomListModel.categoryName(section)
0284 QQC2.ToolTip.visible: hovered
0285 QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
0286 }
0287 }
0288 }
0289
0290 reuseItems: true
0291 currentIndex: -1 // we don't want any room highlighted by default
0292
0293 delegate: root.collapsed ? collapsedModeListComponent : normalModeListComponent
0294
0295 Component {
0296 id: collapsedModeListComponent
0297
0298 CollapsedRoomDelegate {
0299 filterText: sortFilterRoomListModel.filterText
0300 }
0301 }
0302
0303 Component {
0304 id: normalModeListComponent
0305
0306 RoomDelegate {
0307 filterText: sortFilterRoomListModel.filterText
0308
0309 connection: root.connection
0310
0311 height: visible ? implicitHeight : 0
0312
0313 visible: categoryVisible || filterText.length > 0
0314 }
0315 }
0316
0317 footer: Delegates.RoundedItemDelegate {
0318 visible: listView.view.count > 0 && spaceDrawer.showDirectChats
0319 text: i18n("Find your friends")
0320 icon.name: "list-add-user"
0321 icon.width: Kirigami.Units.gridUnit * 2
0322 icon.height: Kirigami.Units.gridUnit * 2
0323
0324 onClicked: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {
0325 connection: root.connection
0326 }, {
0327 title: i18nc("@title", "Find your friends")
0328 })
0329 }
0330 }
0331 }
0332 }
0333
0334 footer: Loader {
0335 width: parent.width
0336 sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
0337 }
0338
0339 MouseArea {
0340 anchors.top: parent.top
0341 anchors.bottom: parent.bottom
0342 parent: applicationWindow().overlay.parent
0343
0344 x: root.currentWidth - width / 2
0345 width: Kirigami.Units.smallSpacing * 2
0346 z: root.z + 1
0347 enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
0348 visible: enabled
0349 cursorShape: Qt.SplitHCursor
0350
0351 property int _lastX
0352
0353 onPressed: mouse => {
0354 _lastX = mouse.x;
0355 }
0356 onPositionChanged: mouse => {
0357 if (_lastX == -1) {
0358 return;
0359 }
0360 if (mouse.x > _lastX) {
0361 // we moved to the right
0362 if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
0363 // Here we get back directly to a more wide mode.
0364 _private.currentWidth = _private.defaultWidth;
0365 Config.collapsed = false;
0366 } else if (_private.currentWidth >= _private.collapseWidth) {
0367 // Increase page width
0368 _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
0369 }
0370 } else if (mouse.x < _lastX) {
0371 const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
0372 if (tmpWidth < _private.collapseWidth) {
0373 _private.currentWidth = Qt.binding(() => _private.collapsedSize);
0374 Config.collapsed = true;
0375 } else {
0376 _private.currentWidth = tmpWidth;
0377 }
0378 }
0379 }
0380 }
0381
0382 Component {
0383 id: userInfo
0384 UserInfo {
0385 visible: !root.collapsed
0386 bottomEdge: false
0387 connection: root.connection
0388 }
0389 }
0390
0391 Component {
0392 id: userInfoDesktop
0393 UserInfoDesktop {
0394 visible: !root.collapsed
0395 connection: root.connection
0396 }
0397 }
0398
0399 Component {
0400 id: exploreComponent
0401 ExploreComponent {
0402 desiredWidth: root.width - Kirigami.Units.largeSpacing
0403 collapsed: root.collapsed
0404 connection: root.connection
0405 }
0406 }
0407
0408 Component {
0409 id: exploreComponentMobile
0410 ExploreComponentMobile {
0411 connection: root.connection
0412
0413 onTextChanged: newText => {
0414 sortFilterRoomListModel.filterText = newText;
0415 }
0416 }
0417 }
0418
0419 /*
0420 * Hold the modifiable currentWidth in a private object so that only internal
0421 * members can modify it.
0422 */
0423 QtObject {
0424 id: _private
0425 property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth
0426 readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
0427 readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
0428 readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
0429 }
0430 }