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 }