import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls.Material 2.12 import Spectral.Component 2.0 import Spectral.Dialog 2.0 import Spectral.Menu 2.0 import Spectral.Effect 2.0 import Spectral 0.1 import Spectral.Setting 0.1 import SortFilterProxyModel 0.2 Item { property var connection: null readonly property var user: connection ? connection.localUser : null property int filter: 0 property var enteredRoom: null signal enterRoom(var room) signal leaveRoom(var room) id: root RoomListModel { id: roomListModel connection: root.connection onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon) } SortFilterProxyModel { id: sortedRoomListModel sourceModel: roomListModel proxyRoles: ExpressionRole { name: "display" expression: { switch (category) { case 1: return "Invited" case 2: return "Favorites" case 3: return "People" case 4: return "Rooms" case 5: return "Low Priority" } } } sorters: [ RoleSorter { roleName: "category" }, RoleSorter { roleName: "lastActiveTime" sortOrder: Qt.DescendingOrder } ] filters: [ ExpressionFilter { expression: joinState != "upgraded" }, RegExpFilter { roleName: "name" pattern: searchField.text caseSensitivity: Qt.CaseInsensitive }, ExpressionFilter { enabled: filter === 1 expression: unreadCount > 0 }, ExpressionFilter { enabled: filter === 2 expression: category === 1 || category === 2 || category === 3 }, ExpressionFilter { enabled: filter === 3 expression: category === 4 || category === 5 } ] } ColumnLayout { anchors.fill: parent spacing: 0 Control { Layout.fillWidth: true Layout.preferredHeight: 64 id: roomListHeader topPadding: 12 bottomPadding: 12 leftPadding: 12 rightPadding: 18 contentItem: RowLayout { ToolButton { Layout.preferredWidth: height Layout.fillHeight: true visible: !searchField.active contentItem: MaterialIcon { icon: { switch (filter) { case 0: return "\ue8b6" case 1: return "\ue7f5" case 2: return "\ue7ff" case 3: return "\ue7fc" } } } Menu { id: filterMenu MenuItem { text: "All" onClicked: filter = 0 } MenuSeparator {} MenuItem { text: "New" onClicked: filter = 1 } MenuItem { text: "People" onClicked: filter = 2 } MenuItem { text: "Group" onClicked: filter = 3 } } onClicked: filterMenu.popup() } ToolButton { Layout.preferredWidth: height Layout.fillHeight: true visible: searchField.active contentItem: MaterialIcon { icon: "\ue5cd" } onClicked: searchField.clear() } AutoTextField { readonly property bool active: text Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter id: searchField placeholderText: "Search..." color: MPalette.lighter } Avatar { Layout.preferredWidth: height Layout.fillHeight: true Layout.alignment: Qt.AlignRight visible: !searchField.active source: root.user ? root.user.avatarMediaId : null hint: root.user ? root.user.displayName : "?" RippleEffect { anchors.fill: parent circular: true onClicked: accountDetailDialog.createObject(ApplicationWindow.overlay).open() } } } background: Rectangle { color: Material.background opacity: listView.atYBeginning ? 0 : 1 layer.enabled: true layer.effect: ElevationEffect { elevation: 2 } } } AutoListView { Layout.fillWidth: true Layout.fillHeight: true id: listView spacing: 0 clip: true model: sortedRoomListModel boundsBehavior: Flickable.DragOverBounds ScrollBar.vertical: ScrollBar {} delegate: Item { width: listView.width height: 64 Rectangle { anchors.fill: parent visible: currentRoom === enteredRoom color: Material.accent opacity: 0.1 } Rectangle { width: unreadCount >= 0 ? 4 : 0 height: parent.height color: Material.accent Behavior on width { PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } } } RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 12 Avatar { Layout.preferredWidth: height Layout.fillHeight: true source: avatar hint: name || "No Name" } ColumnLayout { Layout.fillWidth: true Layout.fillHeight: true Layout.alignment: Qt.AlignHCenter Label { Layout.fillWidth: true Layout.fillHeight: true text: name || "No Name" color: MPalette.foreground font.pixelSize: 16 elide: Text.ElideRight wrapMode: Text.NoWrap } Label { Layout.fillWidth: true Layout.fillHeight: true text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,"") color: MPalette.lighter font.pixelSize: 13 elide: Text.ElideRight wrapMode: Text.NoWrap } } Label { visible: notificationCount > 0 && highlightCount == 0 color: "white" text: notificationCount leftPadding: 12 rightPadding: 12 topPadding: 4 bottomPadding: 4 font.bold: true background: Rectangle { radius: height / 2 color: MPalette.lighter } } Label { visible: highlightCount > 0 color: "white" text: highlightCount leftPadding: 12 rightPadding: 12 topPadding: 4 bottomPadding: 4 font.bold: true background: Rectangle { radius: height / 2 color: MPalette.accent } } } RippleEffect { anchors.fill: parent onPrimaryClicked: { if (category === RoomType.Invited) { acceptInvitationDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom}).open() } else { if (enteredRoom) { enteredRoom.displayed = false leaveRoom(enteredRoom) } currentRoom.displayed = true enterRoom(currentRoom) enteredRoom = currentRoom } } onSecondaryClicked: roomListContextMenu.createObject(ApplicationWindow.overlay, {"room": currentRoom}).popup() } Component { id: roomListContextMenu RoomListContextMenu {} } } section.property: "display" section.criteria: ViewSection.FullString section.delegate: Label { width: parent.width height: 24 text: section color: MPalette.lighter leftPadding: 16 elide: Text.ElideRight verticalAlignment: Text.AlignVCenter } } } Component { id: acceptInvitationDialog AcceptInvitationDialog {} } }