diff --git a/imports/Spectral/Component/Timeline/AutoImage.qml b/imports/Spectral/Component/AutoImage.qml similarity index 100% rename from imports/Spectral/Component/Timeline/AutoImage.qml rename to imports/Spectral/Component/AutoImage.qml diff --git a/imports/Spectral/Component/Timeline/AutoLabel.qml b/imports/Spectral/Component/AutoLabel.qml similarity index 100% rename from imports/Spectral/Component/Timeline/AutoLabel.qml rename to imports/Spectral/Component/AutoLabel.qml diff --git a/imports/Spectral/Component/AutoTextArea.qml b/imports/Spectral/Component/AutoTextArea.qml new file mode 100644 index 0000000..f59efa9 --- /dev/null +++ b/imports/Spectral/Component/AutoTextArea.qml @@ -0,0 +1,113 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +TextArea { + property real progress: 0 + + wrapMode: Text.Wrap + placeholderText: "Send a Message" + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + selectByMouse: true + verticalAlignment: TextEdit.AlignVCenter + + text: currentRoom ? currentRoom.cachedInput : "" + + background: Item { + } + + Timer { + id: timeoutTimer + + repeat: false + interval: 2000 + onTriggered: { + repeatTimer.stop() + currentRoom.sendTypingNotification(false) + } + } + + Timer { + id: repeatTimer + + repeat: true + interval: 5000 + triggeredOnStart: true + onTriggered: currentRoom.sendTypingNotification(true) + } + + ToolTip.visible: currentRoom + && currentRoom.hasUsersTyping + ToolTip.text: currentRoom ? currentRoom.usersTyping : "" + + Keys.onReturnPressed: { + if (event.modifiers & Qt.ShiftModifier) { + insert(cursorPosition, "\n") + } else { + postMessage(text) + text = "" + } + } + + onTextChanged: { + timeoutTimer.restart() + repeatTimer.start() + currentRoom.cachedInput = text + } + + function postMessage(text) { + if (text.trim().length === 0) { return } + if(!currentRoom) { return } + + var PREFIX_ME = '/me ' + var PREFIX_NOTICE = '/notice ' + var PREFIX_RAINBOW = '/rainbow ' + var PREFIX_HTML = '/html ' + var PREFIX_MARKDOWN = '/md ' + + var replyRe = new RegExp("^> <(.*)><(.*)> (.*)\n\n(.*)") + if (text.match(replyRe)) { + var matches = text.match(replyRe) + currentRoom.sendReply(matches[1], matches[2], matches[3], matches[4]) + return + } + + if (text.indexOf(PREFIX_ME) === 0) { + text = text.substr(PREFIX_ME.length) + currentRoom.postMessage(text, RoomMessageEvent.Emote) + return + } + if (text.indexOf(PREFIX_NOTICE) === 0) { + text = text.substr(PREFIX_NOTICE.length) + currentRoom.postMessage(text, RoomMessageEvent.Notice) + return + } + if (text.indexOf(PREFIX_RAINBOW) === 0) { + text = text.substr(PREFIX_RAINBOW.length) + + var parsedText = "" + var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] + for (var i = 0; i < text.length; i++) { + parsedText = parsedText + "" + text.charAt(i) + "" + } + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_HTML) === 0) { + text = text.substr(PREFIX_HTML.length) + var re = new RegExp("<.*?>") + var plainText = text.replace(re, "") + currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_MARKDOWN) === 0) { + text = text.substr(PREFIX_MARKDOWN.length) + var parsedText = Markdown.markdown_parser(text) + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + + currentRoom.postPlainText(text) + } +} diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index b38bc1f..1c9a70b 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -6,6 +6,8 @@ import QtQuick.Controls.Material 2.2 import Spectral 0.1 import Spectral.Setting 0.1 +import Spectral.Component 2.0 + import "qrc:/js/util.js" as Util RowLayout { diff --git a/imports/Spectral/Component/qmldir b/imports/Spectral/Component/qmldir index 09541c6..edf7d5c 100644 --- a/imports/Spectral/Component/qmldir +++ b/imports/Spectral/Component/qmldir @@ -2,3 +2,6 @@ module Spectral.Component AutoMouseArea 2.0 AutoMouseArea.qml MaterialIcon 2.0 MaterialIcon.qml SideNavButton 2.0 SideNavButton.qml +AutoImage 2.0 AutoImage.qml +AutoLabel 2.0 AutoLabel.qml +AutoTextArea 2.0 AutoTextArea.qml diff --git a/imports/Spectral/Form/RoomForm.qml b/imports/Spectral/Form/RoomForm.qml deleted file mode 100644 index 157a939..0000000 --- a/imports/Spectral/Form/RoomForm.qml +++ /dev/null @@ -1,539 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtQuick.Controls.Material 2.2 - -import Spectral.Component 2.0 -import Spectral.Component.Emoji 2.0 -import Spectral.Component.Timeline 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 - -import "qrc:/js/md.js" as Markdown -import "qrc:/js/util.js" as Util - -Item { - property var currentRoom: null - - id: item - - MessageEventModel { - id: messageEventModel - room: currentRoom - } - - RoomDrawer { - width: Math.min(item.width * 0.7, 480) - height: item.height - - id: roomDrawer - - room: currentRoom - } - - Label { - anchors.centerIn: parent - visible: !currentRoom - text: "Please choose a room." - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - visible: currentRoom - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 64 - - z: 10 - - color: Material.accent - - ItemDelegate { - anchors.fill: parent - - onClicked: roomDrawer.open() - - RowLayout { - anchors.fill: parent - anchors.margins: 12 - - spacing: 12 - - ImageItem { - Layout.preferredWidth: height - Layout.fillHeight: true - - hint: currentRoom ? currentRoom.displayName : "No name" - image: spectralController.safeImage(currentRoom ? currentRoom.avatar : null) - } - - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - - visible: parent.width > 64 - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: currentRoom ? currentRoom.displayName : "" - color: "white" - font.pointSize: 12 - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : "" - color: "white" - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - } - } - } - } - - ProgressBar { - Layout.fillWidth: true - z: 10 - - visible: currentRoom && currentRoom.busy - indeterminate: true - } - - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - id: messageListView - - displayMarginBeginning: 40 - displayMarginEnd: 40 - verticalLayoutDirection: ListView.BottomToTop - spacing: 8 - - cacheBuffer: 200 - - boundsBehavior: Flickable.DragOverBounds - - property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1 - - onContentYChanged: { - if(verticalVelocity < 0 && contentY - 5000 < originY) - currentRoom.getPreviousContent(50); - } - - onMovementEnded: { - currentRoom.saveViewport(sortedMessageEventModel.mapToSource(indexAt(contentX, contentY)), sortedMessageEventModel.mapToSource(largestVisibleIndex)) - var newReadMarker = sortedMessageEventModel.get(largestVisibleIndex).eventId - if (newReadMarker) currentRoom.readMarkerEventId = newReadMarker - } - - displaced: Transition { - NumberAnimation { - property: "y"; duration: 200 - easing.type: Easing.OutQuad - } - } - - model: SortFilterProxyModel { - id: sortedMessageEventModel - - sourceModel: messageEventModel - - filters: ExpressionFilter { - expression: marks !== 0x08 && marks !== 0x10 - } - - onModelReset: { - if (currentRoom) - { - var lastScrollPosition = mapFromSource(currentRoom.savedTopVisibleIndex()) - if (lastScrollPosition === 0) - messageListView.positionViewAtBeginning() - else - { - console.log("Scrolling to position", lastScrollPosition) - messageListView.currentIndex = lastScrollPosition - } - if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize === 0) - currentRoom.getPreviousContent(100) - } - console.log("Model timeline reset") - } - } - - delegate: ColumnLayout { - width: parent.width - - id: delegateColumn - - spacing: 8 - - Label { - Layout.alignment: Qt.AlignHCenter - - visible: section !== aboveSection - - text: section - color: "white" - verticalAlignment: Text.AlignVCenter - leftPadding: 8 - rightPadding: 8 - topPadding: 4 - bottomPadding: 4 - - background: Rectangle { - color: MSettings.darkTheme ? "#484848" : "grey" - } - } - - MessageDelegate { - visible: eventType === "notice" || eventType === "message" || eventType === "image" || eventType === "video" || eventType === "audio" || eventType === "file" - } - - StateDelegate { - Layout.maximumWidth: messageListView.width * 0.8 - - visible: eventType === "emote" || eventType === "state" - } - - Label { - Layout.alignment: Qt.AlignHCenter - - visible: eventType === "other" - - text: display - color: "grey" - font.italic: true - } - - Label { - Layout.alignment: Qt.AlignHCenter - - visible: readMarker === true && index !== 0 - - text: "And Now" - color: "white" - verticalAlignment: Text.AlignVCenter - leftPadding: 8 - rightPadding: 8 - topPadding: 4 - bottomPadding: 4 - - background: Rectangle { color: MSettings.darkTheme ? "#484848" : "grey" } - } - } - - RoundButton { - width: 64 - height: 64 - - id: goTopFab - - visible: !(parent.atYEnd || messageListView.moving) - - anchors.right: parent.right - anchors.bottom: parent.bottom - - contentItem: MaterialIcon { - anchors.fill: parent - - icon: "\ue313" - color: "white" - } - - Material.background: Material.accent - - onClicked: parent.positionViewAtBeginning() - - Behavior on opacity { NumberAnimation { duration: 200 } } - } - - MessageContextMenu { id: messageContextMenu } - - Popup { - property string sourceText - - x: (window.width - width) / 2 - y: (window.height - height) / 2 - width: 480 - - id: sourceDialog - - parent: ApplicationWindow.overlay - - modal: true - - padding: 16 - - closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside - - contentItem: ScrollView { - TextArea { - readOnly: true - selectByMouse: true - - text: sourceDialog.sourceText - } - } - } - - Popup { - property alias listModel: readMarkerListView.model - - x: (window.width - width) / 2 - y: (window.height - height) / 2 - width: 320 - - id: readMarkerDialog - - parent: ApplicationWindow.overlay - - modal: true - padding: 16 - - closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside - - contentItem: ListView { - implicitHeight: Math.min(window.height - 64, readMarkerListView.contentHeight) - - id: readMarkerListView - - clip: true - boundsBehavior: Flickable.DragOverBounds - - delegate: ItemDelegate { - width: parent.width - height: 48 - - RowLayout { - anchors.fill: parent - anchors.margins: 8 - spacing: 12 - - ImageItem { - Layout.preferredWidth: height - Layout.fillHeight: true - - image: modelData.avatar - hint: modelData.displayName - } - - Label { - Layout.fillWidth: true - - text: modelData.displayName - } - } - } - - ScrollBar.vertical: ScrollBar {} - } - } - } - - Item { - Layout.fillWidth: true - Layout.preferredHeight: 40 - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 40 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: Material.background - - Rectangle { - anchors.verticalCenter: parent.top - width: parent.width - height: 48 - - color: MSettings.darkTheme ? "#303030" : "#fafafa" - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 2 - } - - RowLayout { - anchors.fill: parent - - spacing: 0 - - ItemDelegate { - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - - contentItem: MaterialIcon { icon: "\ue226" } - - onClicked: currentRoom.chooseAndUploadFile() - } - - ScrollView { - Layout.fillWidth: true - Layout.preferredHeight: 48 - - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - - clip: true - - TextArea { - property real progress: 0 - - id: inputField - - wrapMode: Text.Wrap - placeholderText: "Send a Message" - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - selectByMouse: true - verticalAlignment: TextEdit.AlignVCenter - - text: currentRoom ? currentRoom.cachedInput : "" - - background: Item {} - - onTextChanged: { - timeoutTimer.restart() - repeatTimer.start() - currentRoom.cachedInput = text - } - - ToolTip.visible: currentRoom && currentRoom.hasUsersTyping - ToolTip.text: currentRoom ? currentRoom.usersTyping : "" - - Keys.onReturnPressed: { - if (event.modifiers & Qt.ShiftModifier) { - inputField.insert(inputField.cursorPosition, "\n") - } else { - inputField.postMessage(inputField.text) - inputField.text = "" - } - } - - Timer { - id: timeoutTimer - - repeat: false - interval: 2000 - onTriggered: { - repeatTimer.stop() - currentRoom.sendTypingNotification(false) - } - } - - Timer { - id: repeatTimer - - repeat: true - interval: 5000 - triggeredOnStart: true - onTriggered: currentRoom.sendTypingNotification(true) - } - - function postMessage(text) { - if (text.trim().length === 0) { return } - if(!currentRoom) { return } - - var PREFIX_ME = '/me ' - var PREFIX_NOTICE = '/notice ' - var PREFIX_RAINBOW = '/rainbow ' - var PREFIX_HTML = '/html ' - var PREFIX_MARKDOWN = '/md ' - - var replyRe = new RegExp("^> <(.*)><(.*)> (.*)\n\n(.*)") - if (text.match(replyRe)) { - var matches = text.match(replyRe) - currentRoom.sendReply(matches[1], matches[2], matches[3], matches[4]) - return - } - - if (text.indexOf(PREFIX_ME) === 0) { - text = text.substr(PREFIX_ME.length) - currentRoom.postMessage(text, RoomMessageEvent.Emote) - return - } - if (text.indexOf(PREFIX_NOTICE) === 0) { - text = text.substr(PREFIX_NOTICE.length) - currentRoom.postMessage(text, RoomMessageEvent.Notice) - return - } - if (text.indexOf(PREFIX_RAINBOW) === 0) { - text = text.substr(PREFIX_RAINBOW.length) - - var parsedText = "" - var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] - for (var i = 0; i < text.length; i++) { - parsedText = parsedText + "" + text.charAt(i) + "" - } - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_HTML) === 0) { - text = text.substr(PREFIX_HTML.length) - var re = new RegExp("<.*?>") - var plainText = text.replace(re, "") - currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_MARKDOWN) === 0) { - text = text.substr(PREFIX_MARKDOWN.length) - var parsedText = Markdown.markdown_parser(text) - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - - currentRoom.postPlainText(text) - } - } - } - - ItemDelegate { - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - - id: emojiButton - - contentItem: MaterialIcon { icon: "\ue24e" } - - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open() - - EmojiPicker { - x: window.width - 370 - y: window.height - 400 - - width: 360 - height: 320 - - id: emojiPicker - - parent: ApplicationWindow.overlay - - Material.elevation: 2 - - textArea: inputField - } - } - } - } - } - } -} diff --git a/imports/Spectral/Form/RoomListForm.qml b/imports/Spectral/Form/RoomListForm.qml deleted file mode 100644 index 723df91..0000000 --- a/imports/Spectral/Form/RoomListForm.qml +++ /dev/null @@ -1,266 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.0 -import QtQuick.Controls.Material 2.2 -import QtQml.Models 2.3 - -import Spectral.Component 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 - -import "qrc:/js/util.js" as Util - -Rectangle { - property alias listModel: sortedRoomListModel.sourceModel - property int filter: 0 - property var enteredRoom: null - - color: MSettings.darkTheme ? "#323232" : "#f3f3f3" - - Label { - text: MSettings.miniMode ? "Empty" : "Here? No, not here." - anchors.centerIn: parent - visible: listView.count === 0 - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - TextField { - Layout.fillWidth: true - Layout.preferredHeight: 40 - Layout.margins: 12 - - id: searchField - - leftPadding: MSettings.miniMode ? 4 : 32 - topPadding: 0 - bottomPadding: 0 - placeholderText: "Search..." - - background: Rectangle { - color: MSettings.darkTheme ? "#303030" : "#fafafa" - layer.enabled: true - layer.effect: ElevationEffect { - elevation: searchField.focus ? 2 : 1 - } - } - - Shortcut { - sequence: StandardKey.Find - onActivated: searchField.forceActiveFocus() - } - } - - SortFilterProxyModel { - id: sortedRoomListModel - - proxyRoles: ExpressionRole { - name: "display" - expression: { - switch (category) { - case 1: return "Invited" - case 2: return "Favorites" - case 3: return "Rooms" - case 4: return "People" - case 5: return "Low Priority" - } - } - } - - sorters: [ - RoleSorter { roleName: "category" }, - RoleSorter { - roleName: "lastActiveTime" - sortOrder: Qt.DescendingOrder - } - ] - } - - SortFilterProxyModel { - id: roomListProxyModel - - sourceModel: sortedRoomListModel - - filters: [ - 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 === 4 - }, - ExpressionFilter { - enabled: filter === 3 - expression: category === 3 || category === 5 - } - ] - } - - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - - id: listView - - spacing: 1 - clip: true - - model: roomListProxyModel - - currentIndex: -1 - - highlightFollowsCurrentItem: true - - highlightMoveDuration: 200 - highlightResizeDuration: 0 - - boundsBehavior: Flickable.DragOverBounds - - ScrollBar.vertical: ScrollBar {} - - delegate: Rectangle { - readonly property bool highlighted: currentRoom === enteredRoom - - width: parent.width - height: 64 - - color: MSettings.darkTheme ? "#303030" : "#fafafa" - - AutoMouseArea { - anchors.fill: parent - - hoverEnabled: MSettings.miniMode - - onSecondaryClicked: { - roomContextMenu.model = model - roomContextMenu.popup() - } - onPrimaryClicked: { - if (category === RoomType.Invited) { - inviteDialog.currentRoom = currentRoom - inviteDialog.open() - } else { - enteredRoom = currentRoom - } - } - - ToolTip.visible: MSettings.miniMode && containsMouse - ToolTip.text: name - } - - Rectangle { - anchors.fill: parent - - visible: highlightCount > 0 || highlighted - color: Material.accent - opacity: 0.1 - } - - Rectangle { - width: unreadCount > 0 || highlighted ? 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 - - ImageItem { - id: imageItem - - Layout.preferredWidth: height - Layout.fillHeight: true - - hint: name || "No Name" - - image: avatar - } - - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignHCenter - - visible: parent.width > 64 - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: name || "No Name" - font.pointSize: 12 - 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,"") - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - } - } - } - - section.property: "display" - section.criteria: ViewSection.FullString - section.delegate: Label { - width: parent.width - height: 24 - - text: section - color: "grey" - leftPadding: MSettings.miniMode ? undefined : 16 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined - } - - RoomContextMenu { id: roomContextMenu } - - Dialog { - property var currentRoom - - id: inviteDialog - parent: ApplicationWindow.overlay - - x: (window.width - width) / 2 - y: (window.height - height) / 2 - width: 360 - - title: "Action Required" - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel - - contentItem: Label { text: "Accept this invitation?" } - - onAccepted: currentRoom.acceptInvitation() - onRejected: currentRoom.forget() - } - } - } -} diff --git a/imports/Spectral/Form/qmldir b/imports/Spectral/Form/qmldir deleted file mode 100644 index b02e012..0000000 --- a/imports/Spectral/Form/qmldir +++ /dev/null @@ -1,4 +0,0 @@ -module Spectral.Form -RoomForm 2.0 RoomForm.qml -RoomListForm 2.0 RoomListForm.qml - diff --git a/imports/Spectral/Menu/qmldir b/imports/Spectral/Menu/qmldir index 031159a..6892ba8 100644 --- a/imports/Spectral/Menu/qmldir +++ b/imports/Spectral/Menu/qmldir @@ -1,4 +1,3 @@ module Spectral.Menu MessageContextMenu 2.0 MessageContextMenu.qml RoomContextMenu 2.0 RoomContextMenu.qml - diff --git a/imports/Spectral/Page/Login.qml b/imports/Spectral/Page/Login.qml index 85c83b2..6476ee4 100644 --- a/imports/Spectral/Page/Login.qml +++ b/imports/Spectral/Page/Login.qml @@ -1,162 +1,20 @@ import QtQuick 2.9 -import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.0 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 -import Spectral.Component 2.0 - -import Spectral.Setting 0.1 - -Page { - property var controller - - Row { - anchors.fill: parent - - Pane { - width: parent.width / 2 - height: parent.height - - background: Item { - Image { - id: background - anchors.fill: parent - source: "qrc:/assets/img/background.jpg" - fillMode: Image.PreserveAspectCrop - cache: false - } - - ColorOverlay { - anchors.fill: background - source: background - color: Material.accent - opacity: 0.7 - } - } - - Column { - x: 32 - anchors.verticalCenter: parent.verticalCenter - - Label { - text: "MATRIX LOGIN." - font.pointSize: 28 - font.bold: true - color: "white" - } - - Label { - text: "A NEW METHOD OF MESSAGING" - font.pointSize: 12 - color: "white" - } - } +LoginForm { + loginButton.onClicked: { + if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) { + loginButtonTooltip.text = "Server address should start with http(s)://" + loginButtonTooltip.open() + return + } + if (!(usernameField.text.startsWith("@") && usernameField.text.includes(":"))) { + loginButtonTooltip.text = "Username should be in format of @example:example.com" + loginButtonTooltip.open() + return } - Pane { - width: parent.width / 2 - height: parent.height + controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text) - padding: 64 - - ColumnLayout { - width: parent.width - - id: mainCol - - TextField { - Layout.fillWidth: true - - id: serverField - - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - - text: "https://matrix.org" - placeholderText: "Server" - - background: Rectangle { - implicitHeight: 48 - - color: MSettings.darkTheme ? "#242424" : "#eaeaea" - border.color: parent.activeFocus ? Material.accent : "transparent" - border.width: 2 - } - } - - TextField { - Layout.fillWidth: true - - id: usernameField - - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - - placeholderText: "Username" - - background: Rectangle { - implicitHeight: 48 - - color: MSettings.darkTheme ? "#242424" : "#eaeaea" - border.color: parent.activeFocus ? Material.accent : "transparent" - border.width: 2 - } - } - - TextField { - Layout.fillWidth: true - - id: passwordField - - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - - placeholderText: "Password" - echoMode: TextInput.Password - - background: Rectangle { - implicitHeight: 48 - - color: MSettings.darkTheme ? "#242424" : "#eaeaea" - border.color: parent.activeFocus ? Material.accent : "transparent" - border.width: 2 - } - } - - Button { - Layout.fillWidth: true - - id: loginButton - - text: "LOGIN" - highlighted: true - - ToolTip { - id: loginButtonTooltip - } - - onClicked: { - if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) { - loginButtonTooltip.text = "Server address should start with http(s)://" - loginButtonTooltip.open() - return - } - if (!(usernameField.text.startsWith("@") && usernameField.text.includes(":"))) { - loginButtonTooltip.text = "Username should be in format of @example:example.com" - loginButtonTooltip.open() - return - } - - controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text) - - controller.connectionAdded.connect(function() { stackView.pop() }) - } - } - } - } + controller.connectionAdded.connect(function() { stackView.pop() }) } } diff --git a/imports/Spectral/Page/LoginForm.ui.qml b/imports/Spectral/Page/LoginForm.ui.qml new file mode 100644 index 0000000..99b537a --- /dev/null +++ b/imports/Spectral/Page/LoginForm.ui.qml @@ -0,0 +1,147 @@ +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 + +import Spectral.Component 2.0 + +import Spectral.Setting 0.1 + +Page { + property var controller + + property alias loginButton: loginButton + + Row { + anchors.fill: parent + + Pane { + width: parent.width / 2 + height: parent.height + + background: Item { + Image { + id: background + anchors.fill: parent + source: "qrc:/assets/img/background.jpg" + fillMode: Image.PreserveAspectCrop + cache: false + } + + ColorOverlay { + anchors.fill: background + source: background + color: Material.accent + opacity: 0.7 + } + } + + Column { + x: 32 + anchors.verticalCenter: parent.verticalCenter + + Label { + text: "MATRIX LOGIN." + font.pointSize: 28 + font.bold: true + color: "white" + } + + Label { + text: "A NEW METHOD OF MESSAGING" + font.pointSize: 12 + color: "white" + } + } + } + + Pane { + width: parent.width / 2 + height: parent.height + + padding: 64 + + ColumnLayout { + width: parent.width + + id: mainCol + + TextField { + Layout.fillWidth: true + + id: serverField + + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + + text: "https://matrix.org" + placeholderText: "Server" + + background: Rectangle { + implicitHeight: 48 + + color: MSettings.darkTheme ? "#242424" : "#eaeaea" + border.color: parent.activeFocus ? Material.accent : "transparent" + border.width: 2 + } + } + + TextField { + Layout.fillWidth: true + + id: usernameField + + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + + placeholderText: "Username" + + background: Rectangle { + implicitHeight: 48 + + color: MSettings.darkTheme ? "#242424" : "#eaeaea" + border.color: parent.activeFocus ? Material.accent : "transparent" + border.width: 2 + } + } + + TextField { + Layout.fillWidth: true + + id: passwordField + + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + + placeholderText: "Password" + echoMode: TextInput.Password + + background: Rectangle { + implicitHeight: 48 + + color: MSettings.darkTheme ? "#242424" : "#eaeaea" + border.color: parent.activeFocus ? Material.accent : "transparent" + border.width: 2 + } + } + + Button { + Layout.fillWidth: true + + id: loginButton + + text: "LOGIN" + highlighted: true + + ToolTip { + id: loginButtonTooltip + } + } + } + } + } +} diff --git a/imports/Spectral/Page/Room.qml b/imports/Spectral/Page/Room.qml index dc7b5a7..bd55905 100644 --- a/imports/Spectral/Page/Room.qml +++ b/imports/Spectral/Page/Room.qml @@ -1,55 +1,5 @@ import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtQuick.Controls.Material 2.2 -import Spectral.Form 2.0 -import Spectral.Component 2.0 -import Spectral.Effect 2.0 - -import Spectral 0.1 -import Spectral.Setting 0.1 - -Page { - property alias connection: roomListModel.connection - property alias filter: roomListForm.filter - - id: page - - RoomListModel { - id: roomListModel - - onNewMessage: if (!window.active) spectralController.showMessage(roomName, content, icon) - } - - RowLayout { - anchors.fill: parent - - spacing: 0 - - RoomListForm { - Layout.fillHeight: true - Layout.preferredWidth: MSettings.miniMode ? 64 : page.width * 0.35 - Layout.minimumWidth: 64 - Layout.maximumWidth: 360 - - id: roomListForm - - listModel: roomListModel - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 2 - } - } - - RoomForm { - Layout.fillWidth: true - Layout.fillHeight: true - - id: roomForm - - currentRoom: roomListForm.enteredRoom - } - } +RoomForm { + roomListModel.onNewMessage: if (!window.active) spectralController.showMessage(roomName, content, icon) } diff --git a/imports/Spectral/Page/RoomForm.ui.qml b/imports/Spectral/Page/RoomForm.ui.qml new file mode 100644 index 0000000..0c35fd4 --- /dev/null +++ b/imports/Spectral/Page/RoomForm.ui.qml @@ -0,0 +1,61 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import Spectral.Panel 2.0 +import Spectral.Component 2.0 +import Spectral.Effect 2.0 + +import Spectral 0.1 +import Spectral.Setting 0.1 + +Page { + property alias connection: roomListModel.connection + property alias filter: roomListForm.filter + + property alias roomListModel: roomListModel + + id: page + + RoomListModel { + id: roomListModel + } + + RowLayout { + anchors.fill: parent + + spacing: 0 + + RoomListPanel { + Layout.fillHeight: true + Layout.preferredWidth: MSettings.miniMode ? 64 : page.width * 0.35 + Layout.minimumWidth: 64 + Layout.maximumWidth: 360 + + id: roomListForm + + listModel: roomListModel + + layer.enabled: true + layer.effect: ElevationEffect { + elevation: 2 + } + } + + RoomPanel { + Layout.fillWidth: true + Layout.fillHeight: true + + id: roomForm + + currentRoom: roomListForm.enteredRoom + } + } +} + + +/*##^## Designer { + D{i:0;autoSize:false;height:480;width:640} +} + ##^##*/ diff --git a/imports/Spectral/Page/Setting.qml b/imports/Spectral/Page/Setting.qml index c2523e6..0385eaf 100644 --- a/imports/Spectral/Page/Setting.qml +++ b/imports/Spectral/Page/Setting.qml @@ -1,297 +1,5 @@ import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 -import QtQuick.Layouts 1.3 -import Spectral.Component 2.0 -import Spectral.Effect 2.0 - -import Spectral 0.1 -import Spectral.Setting 0.1 - -import "qrc:/js/util.js" as Util - -Page { - property alias listModel: accountSettingsListView.model - - Page { - id: accountForm - - parent: null - - padding: 64 - - ColumnLayout { - anchors.fill: parent - - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - - id: accountSettingsListView - - boundsBehavior: Flickable.DragOverBounds - - clip: true - - delegate: Column { - property bool expanded: false - - spacing: 8 - - ItemDelegate { - width: accountSettingsListView.width - height: 64 - - Row { - anchors.fill: parent - anchors.margins: 8 - - spacing: 8 - - ImageItem { - width: parent.height - height: parent.height - - hint: user.displayName - image: user.avatar - } - - ColumnLayout { - Label { - text: user.displayName - } - Label { - text: user.id - } - } - } - - onClicked: expanded = !expanded - } - - ColumnLayout { - width: parent.width - 32 - height: expanded ? implicitHeight : 0 - anchors.horizontalCenter: parent.horizontalCenter - - spacing: 0 - - clip: true - - ListView { - Layout.fillWidth: true - Layout.preferredHeight: 24 - - orientation: ListView.Horizontal - - spacing: 8 - - model: ["#498882", "#42a5f5", "#5c6bc0", "#7e57c2", "#ab47bc", "#ff7043"] - - delegate: Rectangle { - width: parent.height - height: parent.height - radius: width / 2 - - color: modelData - - MouseArea { - anchors.fill: parent - - onClicked: spectralController.setColor(connection.localUserId, modelData) - } - } - } - - RowLayout { - Layout.fillWidth: true - - Label { text: "Homeserver:" } - TextField { - Layout.fillWidth: true - - text: connection.homeserver - selectByMouse: true - readOnly: true - } - } - - RowLayout { - Layout.fillWidth: true - - spacing: 16 - - Label { text: "Device ID:" } - TextField { - Layout.fillWidth: true - - text: connection.deviceId - selectByMouse: true - readOnly: true - } - } - - RowLayout { - Layout.fillWidth: true - - spacing: 16 - - Label { text: "Access Token:" } - TextField { - Layout.fillWidth: true - - text: connection.accessToken - selectByMouse: true - readOnly: true - } - } - - Button { - Layout.fillWidth: true - - highlighted: true - text: "Logout" - - onClicked: spectralController.logout(connection) - } - - Behavior on height { - PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } - } - } - } - } - - Button { - Layout.fillWidth: true - - text: "Add Account" - flat: true - highlighted: true - - onClicked: stackView.push(loginPage) - } - } - } - - Page { - id: generalForm - - parent: null - - padding: 64 - - Column { - Switch { - text: "Use press and hold instead of right click" - checked: MSettings.pressAndHold - - onCheckedChanged: MSettings.pressAndHold = checked - } - } - } - - Page { - id: appearanceForm - - parent: null - - padding: 64 - - Column { - Switch { - text: "Dark theme" - checked: MSettings.darkTheme - - onCheckedChanged: MSettings.darkTheme = checked - } - - Switch { - text: "Mini Room List" - checked: MSettings.miniMode - - onCheckedChanged: MSettings.miniMode = checked - } - } - } - - Page { - id: aboutForm - - parent: null - - padding: 64 - - ColumnLayout { - spacing: 16 - Image { - Layout.preferredWidth: 64 - Layout.preferredHeight: 64 - - source: "qrc:/assets/img/icon.png" - } - Label { text: "Spectral, an IM client for the Matrix protocol." } - Label { text: "Released under GNU General Public License, version 3." } - } - } - - Rectangle { - width: 240 - height: parent.height - z: 10 - - id: settingDrawer - - color: MSettings.darkTheme ? "#323232" : "#f3f3f3" - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 4 - } - - Column { - anchors.fill: parent - - ItemDelegate { - width: parent.width - - text: "Account" - onClicked: pushToStack(accountForm) - } - - ItemDelegate { - width: parent.width - - text: "General" - onClicked: pushToStack(generalForm) - } - - ItemDelegate { - width: parent.width - - text: "Appearance" - onClicked: pushToStack(appearanceForm) - } - - ItemDelegate { - width: parent.width - - text: "About" - onClicked: pushToStack(aboutForm) - } - } - } - - StackView { - anchors.fill: parent - anchors.leftMargin: settingDrawer.width - - id: settingStackView - } - - function pushToStack(item) { - settingStackView.clear() - settingStackView.push(item) - } +SettingForm { + addAccountButton.onClicked: stackView.push(loginPage) } diff --git a/imports/Spectral/Page/SettingAccountDelegate.qml b/imports/Spectral/Page/SettingAccountDelegate.qml new file mode 100644 index 0000000..869bf58 --- /dev/null +++ b/imports/Spectral/Page/SettingAccountDelegate.qml @@ -0,0 +1,136 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +import Spectral.Component 2.0 + +import Spectral 0.1 +import Spectral.Setting 0.1 + +Column { + property bool expanded: false + + spacing: 8 + + ItemDelegate { + width: accountSettingsListView.width + height: 64 + + Row { + anchors.fill: parent + anchors.margins: 8 + + spacing: 8 + + ImageItem { + width: parent.height + height: parent.height + + hint: user.displayName + image: user.avatar + } + + ColumnLayout { + Label { + text: user.displayName + } + Label { + text: user.id + } + } + } + + onClicked: expanded = !expanded + } + + ColumnLayout { + width: parent.width - 32 + height: expanded ? implicitHeight : 0 + anchors.horizontalCenter: parent.horizontalCenter + + spacing: 0 + + clip: true + + ListView { + Layout.fillWidth: true + Layout.preferredHeight: 24 + + orientation: ListView.Horizontal + + spacing: 8 + + model: ["#498882", "#42a5f5", "#5c6bc0", "#7e57c2", "#ab47bc", "#ff7043"] + + delegate: Rectangle { + width: parent.height + height: parent.height + radius: width / 2 + + color: modelData + + MouseArea { + anchors.fill: parent + + onClicked: spectralController.setColor(connection.localUserId, modelData) + } + } + } + + RowLayout { + Layout.fillWidth: true + + Label { + text: "Homeserver:" + } + TextField { + Layout.fillWidth: true + + text: connection.homeserver + selectByMouse: true + readOnly: true + } + } + + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Label { + text: "Device ID:" + } + TextField { + Layout.fillWidth: true + + text: connection.deviceId + selectByMouse: true + readOnly: true + } + } + + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Label { + text: "Access Token:" + } + TextField { + Layout.fillWidth: true + + text: connection.accessToken + selectByMouse: true + readOnly: true + } + } + + Button { + Layout.fillWidth: true + + highlighted: true + text: "Logout" + } + } +} diff --git a/imports/Spectral/Page/SettingCategoryDelegate.qml b/imports/Spectral/Page/SettingCategoryDelegate.qml new file mode 100644 index 0000000..e78bfbb --- /dev/null +++ b/imports/Spectral/Page/SettingCategoryDelegate.qml @@ -0,0 +1,11 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +ItemDelegate { + text: category + + onClicked: { + settingStackView.clear() + settingStackView.push([accountForm, generalForm, appearanceForm, aboutForm][form]) + } +} diff --git a/imports/Spectral/Page/SettingForm.ui.qml b/imports/Spectral/Page/SettingForm.ui.qml new file mode 100644 index 0000000..af39118 --- /dev/null +++ b/imports/Spectral/Page/SettingForm.ui.qml @@ -0,0 +1,178 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import QtQuick.Layouts 1.3 + +import Spectral.Component 2.0 +import Spectral.Effect 2.0 + +import Spectral 0.1 +import Spectral.Setting 0.1 + +Page { + property alias listModel: accountSettingsListView.model + + property alias addAccountButton: addAccountButton + + implicitWidth: 400 + implicitHeight: 300 + + Page { + id: accountForm + + parent: null + + padding: 64 + + ColumnLayout { + anchors.fill: parent + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + + id: accountSettingsListView + + boundsBehavior: Flickable.DragOverBounds + + clip: true + + delegate: SettingAccountDelegate {} + } + + Button { + Layout.fillWidth: true + + id: addAccountButton + + text: "Add Account" + flat: true + highlighted: true + } + } + } + + Page { + id: generalForm + + parent: null + + padding: 64 + + Column { + Switch { + text: "Use press and hold instead of right click" + checked: MSettings.pressAndHold + + onCheckedChanged: MSettings.pressAndHold = checked + } + } + } + + Page { + id: appearanceForm + + parent: null + + padding: 64 + + Column { + Switch { + text: "Dark theme" + checked: MSettings.darkTheme + + onCheckedChanged: MSettings.darkTheme = checked + } + + Switch { + text: "Mini Room List" + checked: MSettings.miniMode + + onCheckedChanged: MSettings.miniMode = checked + } + } + } + + Page { + id: aboutForm + + parent: null + + padding: 64 + + ColumnLayout { + spacing: 16 + Image { + Layout.preferredWidth: 64 + Layout.preferredHeight: 64 + + source: "qrc:/assets/img/icon.png" + } + Label { + text: "Spectral, an IM client for the Matrix protocol." + } + Label { + text: "Released under GNU General Public License, version 3." + } + } + } + + Rectangle { + width: 240 + height: parent.height + z: 10 + + id: settingDrawer + + color: MSettings.darkTheme ? "#323232" : "#f3f3f3" + + layer.enabled: true + layer.effect: ElevationEffect { + elevation: 4 + } + + Column { + anchors.fill: parent + + Repeater { + model: ListModel { + ListElement { + category: "Account" + form: 0 + } + ListElement { + category: "General" + form: 1 + } + ListElement { + category: "Appearance" + form: 2 + } + ListElement { + category: "About" + form: 3 + } + } + + delegate: SettingCategoryDelegate { + width: parent.width + } + } + } + } + + StackView { + anchors.fill: parent + anchors.leftMargin: settingDrawer.width + + id: settingStackView + + initialItem: aboutForm + } +} + + +/*##^## Designer { + D{i:0;autoSize:true;height:480;width:640} +} + ##^##*/ diff --git a/imports/Spectral/Page/qmldir b/imports/Spectral/Page/qmldir index 4ebdb69..af81264 100644 --- a/imports/Spectral/Page/qmldir +++ b/imports/Spectral/Page/qmldir @@ -2,4 +2,3 @@ module Spectral.Page Login 2.0 Login.qml Room 2.0 Room.qml Setting 2.0 Setting.qml - diff --git a/imports/Spectral/Form/RoomDrawer.qml b/imports/Spectral/Panel/RoomDrawer.qml similarity index 99% rename from imports/Spectral/Form/RoomDrawer.qml rename to imports/Spectral/Panel/RoomDrawer.qml index a63bd09..17d8871 100644 --- a/imports/Spectral/Form/RoomDrawer.qml +++ b/imports/Spectral/Panel/RoomDrawer.qml @@ -203,4 +203,3 @@ Drawer { } } } - diff --git a/imports/Spectral/Panel/RoomHeader.qml b/imports/Spectral/Panel/RoomHeader.qml new file mode 100644 index 0000000..5532967 --- /dev/null +++ b/imports/Spectral/Panel/RoomHeader.qml @@ -0,0 +1,69 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import Spectral 0.1 + +Rectangle { + property alias image: headerImage.image + property alias topic: headerTopicLabel.text + signal clicked() + + id: header + + color: Material.accent + + ItemDelegate { + anchors.fill: parent + + id: roomHeader + + onClicked: header.clicked() + + RowLayout { + anchors.fill: parent + anchors.margins: 12 + + spacing: 12 + + ImageItem { + Layout.preferredWidth: height + Layout.fillHeight: true + + id: headerImage + + hint: currentRoom ? currentRoom.displayName : "No name" + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + + visible: parent.width > 64 + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + text: currentRoom ? currentRoom.displayName : "" + color: "white" + font.pointSize: 12 + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + id: headerTopicLabel + + color: "white" + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + } + } + } +} diff --git a/imports/Spectral/Panel/RoomListDelegate.qml b/imports/Spectral/Panel/RoomListDelegate.qml new file mode 100644 index 0000000..f1759d5 --- /dev/null +++ b/imports/Spectral/Panel/RoomListDelegate.qml @@ -0,0 +1,101 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import Spectral 0.1 +import Spectral.Setting 0.1 + +import Spectral.Component 2.0 + +Rectangle { + readonly property bool highlighted: currentRoom === enteredRoom + + color: MSettings.darkTheme ? "#303030" : "#fafafa" + + AutoMouseArea { + anchors.fill: parent + + hoverEnabled: MSettings.miniMode + + onSecondaryClicked: { + roomContextMenu.model = model + roomContextMenu.popup() + } + onPrimaryClicked: { + if (category === RoomType.Invited) { + inviteDialog.currentRoom = currentRoom + inviteDialog.open() + } else { + enteredRoom = currentRoom + } + } + + ToolTip.visible: MSettings.miniMode && containsMouse + ToolTip.text: name + } + + Rectangle { + anchors.fill: parent + + visible: highlightCount > 0 || highlighted + color: Material.accent + opacity: 0.1 + } + + Rectangle { + width: unreadCount > 0 || highlighted ? 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 + + ImageItem { + id: imageItem + + Layout.preferredWidth: height + Layout.fillHeight: true + + hint: name || "No Name" + + image: avatar + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + visible: parent.width > 64 + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + text: name || "No Name" + font.pointSize: 12 + 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,"") + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + } + } +} diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml new file mode 100644 index 0000000..d89313a --- /dev/null +++ b/imports/Spectral/Panel/RoomListPanel.qml @@ -0,0 +1,44 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +import SortFilterProxyModel 0.2 + +RoomListPanelForm { + sortedRoomListModel.proxyRoles: ExpressionRole { + name: "display" + expression: { + switch (category) { + case 1: return "Invited" + case 2: return "Favorites" + case 3: return "Rooms" + case 4: return "People" + case 5: return "Low Priority" + } + } + } + + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() + } + + Dialog { + property var currentRoom + + id: inviteDialog + parent: ApplicationWindow.overlay + + x: (window.width - width) / 2 + y: (window.height - height) / 2 + width: 360 + + title: "Action Required" + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel + + contentItem: Label { text: "Accept this invitation?" } + + onAccepted: currentRoom.acceptInvitation() + onRejected: currentRoom.forget() + } +} diff --git a/imports/Spectral/Panel/RoomListPanelForm.ui.qml b/imports/Spectral/Panel/RoomListPanelForm.ui.qml new file mode 100644 index 0000000..7d43f0e --- /dev/null +++ b/imports/Spectral/Panel/RoomListPanelForm.ui.qml @@ -0,0 +1,141 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 +import QtQuick.Controls.Material 2.2 +import QtQml.Models 2.3 + +import Spectral.Component 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 + +import "qrc:/js/util.js" as Util + +Rectangle { + property alias listModel: sortedRoomListModel.sourceModel + property int filter: 0 + property var enteredRoom: null + + property alias searchField: searchField + property alias sortedRoomListModel: sortedRoomListModel + + color: MSettings.darkTheme ? "#323232" : "#f3f3f3" + + Label { + text: MSettings.miniMode ? "Empty" : "Here? No, not here." + anchors.centerIn: parent + visible: listView.count === 0 + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + TextField { + Layout.fillWidth: true + Layout.preferredHeight: 40 + Layout.margins: 12 + + id: searchField + + leftPadding: MSettings.miniMode ? 4 : 32 + topPadding: 0 + bottomPadding: 0 + placeholderText: "Search..." + + background: Rectangle { + color: MSettings.darkTheme ? "#303030" : "#fafafa" + layer.enabled: true + layer.effect: ElevationEffect { + elevation: searchField.focus ? 2 : 1 + } + } + } + + SortFilterProxyModel { + id: sortedRoomListModel + + sorters: [ + RoleSorter { roleName: "category" }, + RoleSorter { + roleName: "lastActiveTime" + sortOrder: Qt.DescendingOrder + } + ] + } + + SortFilterProxyModel { + id: roomListProxyModel + + sourceModel: sortedRoomListModel + + filters: [ + 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 === 4 + }, + ExpressionFilter { + enabled: filter === 3 + expression: category === 3 || category === 5 + } + ] + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + + id: listView + + spacing: 1 + clip: true + + model: roomListProxyModel + + currentIndex: -1 + + highlightFollowsCurrentItem: true + + highlightMoveDuration: 200 + highlightResizeDuration: 0 + + boundsBehavior: Flickable.DragOverBounds + + ScrollBar.vertical: ScrollBar {} + + delegate: RoomListDelegate { + width: parent.width + height: 64 + } + + section.property: "display" + section.criteria: ViewSection.FullString + section.delegate: Label { + width: parent.width + height: 24 + + text: section + color: "grey" + leftPadding: MSettings.miniMode ? undefined : 16 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined + } + + RoomContextMenu { id: roomContextMenu } + } + } +} diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml new file mode 100644 index 0000000..317c975 --- /dev/null +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -0,0 +1,56 @@ +import QtQuick 2.9 + +RoomPanelForm { + roomHeader.onClicked: roomDrawer.open() + roomHeader.image: spectralController.safeImage(currentRoom ? currentRoom.avatar : null) + roomHeader.topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : "" + + sortedMessageEventModel.onModelReset: { + if (currentRoom) + { + var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex()) + if (lastScrollPosition === 0) + messageListView.positionViewAtBeginning() + else + { + console.log("Scrolling to position", lastScrollPosition) + messageListView.currentIndex = lastScrollPosition + } + if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize === 0) + currentRoom.getPreviousContent(100) + } + console.log("Model timeline reset") + } + + messageListView { + property int largestVisibleIndex: messageListView.count > 0 ? messageListView.indexAt(messageListView.contentX, messageListView.contentY + height - 1) : -1 + + onContentYChanged: { + if(messageListView.verticalVelocity < 0 && messageListView.contentY - 5000 < messageListView.originY) + currentRoom.getPreviousContent(50); + } + + onMovementEnded: { + currentRoom.saveViewport(sortedMessageEventModel.mapToSource(messageListView.indexAt(messageListView.contentX, messageListView.contentY)), sortedMessageEventModel.mapToSource(largestVisibleIndex)) + var newReadMarker = sortedMessageEventModel.get(largestVisibleIndex).eventId + if (newReadMarker) currentRoom.readMarkerEventId = newReadMarker + } + + displaced: Transition { + NumberAnimation { + property: "y"; duration: 200 + easing.type: Easing.OutQuad + } + } + } + + goTopFab { + onClicked: messageListView.positionViewAtBeginning() + + Behavior on opacity { NumberAnimation { duration: 200 } } + } + + uploadButton.onClicked: currentRoom.chooseAndUploadFile() + + emojiButton.onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open() +} diff --git a/imports/Spectral/Panel/RoomPanelForm.ui.qml b/imports/Spectral/Panel/RoomPanelForm.ui.qml new file mode 100644 index 0000000..692bd49 --- /dev/null +++ b/imports/Spectral/Panel/RoomPanelForm.ui.qml @@ -0,0 +1,364 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 + +import Spectral.Component 2.0 +import Spectral.Component.Emoji 2.0 +import Spectral.Component.Timeline 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 + +import "qrc:/js/md.js" as Markdown +import "qrc:/js/util.js" as Util + +Item { + property var currentRoom: null + + property alias roomHeader: roomHeader + property alias messageListView: messageListView + property alias goTopFab: goTopFab + property alias uploadButton: uploadButton + property alias emojiButton: emojiButton + property alias emojiPicker: emojiPicker + property alias sortedMessageEventModel: sortedMessageEventModel + property alias inputField: inputField + property alias roomDrawer: roomDrawer + + id: root + + MessageEventModel { + id: messageEventModel + room: currentRoom + } + + RoomDrawer { + width: Math.min(root.width * 0.7, 480) + height: root.height + + id: roomDrawer + + room: currentRoom + } + + Label { + anchors.centerIn: parent + visible: !currentRoom + text: "Please choose a room." + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + visible: currentRoom + + RoomHeader { + Layout.fillWidth: true + Layout.preferredHeight: 64 + z: 10 + + id: roomHeader + } + + ProgressBar { + Layout.fillWidth: true + z: 10 + + visible: currentRoom && currentRoom.busy + indeterminate: true + } + + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + id: messageListView + + displayMarginBeginning: 40 + displayMarginEnd: 40 + verticalLayoutDirection: ListView.BottomToTop + spacing: 8 + + boundsBehavior: Flickable.DragOverBounds + + model: SortFilterProxyModel { + id: sortedMessageEventModel + + sourceModel: messageEventModel + + filters: ExpressionFilter { + expression: marks !== 0x08 && marks !== 0x10 + } + } + + delegate: ColumnLayout { + width: parent.width + + id: delegateColumn + + spacing: 8 + + Label { + Layout.alignment: Qt.AlignHCenter + + visible: section !== aboveSection + + text: section + color: "white" + verticalAlignment: Text.AlignVCenter + leftPadding: 8 + rightPadding: 8 + topPadding: 4 + bottomPadding: 4 + + background: Rectangle { + color: MSettings.darkTheme ? "#484848" : "grey" + } + } + + MessageDelegate { + visible: eventType === "notice" || eventType === "message" + || eventType === "image" || eventType === "video" + || eventType === "audio" || eventType === "file" + } + + StateDelegate { + Layout.maximumWidth: messageListView.width * 0.8 + + visible: eventType === "emote" || eventType === "state" + } + + Label { + Layout.alignment: Qt.AlignHCenter + + visible: eventType === "other" + + text: display + color: "grey" + font.italic: true + } + + Label { + Layout.alignment: Qt.AlignHCenter + + visible: readMarker === true && index !== 0 + + text: "And Now" + color: "white" + verticalAlignment: Text.AlignVCenter + leftPadding: 8 + rightPadding: 8 + topPadding: 4 + bottomPadding: 4 + + background: Rectangle { + color: MSettings.darkTheme ? "#484848" : "grey" + } + } + } + + RoundButton { + width: 64 + height: 64 + + id: goTopFab + + visible: !(parent.atYEnd || messageListView.moving) + + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentItem: MaterialIcon { + anchors.fill: parent + + icon: "\ue313" + color: "white" + } + + Material.background: Material.accent + } + + MessageContextMenu { + id: messageContextMenu + } + + Popup { + property string sourceText + + x: (window.width - width) / 2 + y: (window.height - height) / 2 + width: 480 + + id: sourceDialog + + parent: ApplicationWindow.overlay + + modal: true + + padding: 16 + + closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside + + contentItem: ScrollView { + TextArea { + readOnly: true + selectByMouse: true + + text: sourceDialog.sourceText + } + } + } + + Popup { + property alias listModel: readMarkerListView.model + + x: (window.width - width) / 2 + y: (window.height - height) / 2 + width: 320 + + id: readMarkerDialog + + parent: ApplicationWindow.overlay + + modal: true + padding: 16 + + closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside + + contentItem: ListView { + implicitHeight: Math.min(window.height - 64, + readMarkerListView.contentHeight) + + id: readMarkerListView + + clip: true + boundsBehavior: Flickable.DragOverBounds + + delegate: ItemDelegate { + width: parent.width + height: 48 + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + spacing: 12 + + ImageItem { + Layout.preferredWidth: height + Layout.fillHeight: true + + image: modelData.avatar + hint: modelData.displayName + } + + Label { + Layout.fillWidth: true + + text: modelData.displayName + } + } + } + + ScrollBar.vertical: ScrollBar {} + } + } + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: 40 + } + + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + color: Material.background + + Rectangle { + anchors.verticalCenter: parent.top + width: parent.width + height: 48 + + color: MSettings.darkTheme ? "#303030" : "#fafafa" + + layer.enabled: true + layer.effect: ElevationEffect { + elevation: 2 + } + + RowLayout { + anchors.fill: parent + + spacing: 0 + + ItemDelegate { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + + id: uploadButton + + contentItem: MaterialIcon { + icon: "\ue226" + } + } + + ScrollView { + Layout.fillWidth: true + Layout.preferredHeight: 48 + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + clip: true + + AutoTextArea { + id: inputField + } + } + + ItemDelegate { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + + id: emojiButton + + contentItem: MaterialIcon { + icon: "\ue24e" + } + + EmojiPicker { + x: window.width - 370 + y: window.height - 400 + + width: 360 + height: 320 + + id: emojiPicker + + parent: ApplicationWindow.overlay + + Material.elevation: 2 + + textArea: inputField + } + } + } + } + } + } +} + + +/*##^## Designer { + D{i:0;autoSize:true;height:480;width:640} +} + ##^##*/ diff --git a/imports/Spectral/Panel/qmldir b/imports/Spectral/Panel/qmldir new file mode 100644 index 0000000..3e9cc65 --- /dev/null +++ b/imports/Spectral/Panel/qmldir @@ -0,0 +1,3 @@ +module Spectral.Panel +RoomPanel 2.0 RoomPanel.qml +RoomListPanel 2.0 RoomListPanel.qml diff --git a/res.qrc b/res.qrc index 560dcb0..2bd1e2c 100644 --- a/res.qrc +++ b/res.qrc @@ -7,8 +7,6 @@ imports/Spectral/Component/Emoji/EmojiButton.qml imports/Spectral/Component/Emoji/EmojiPicker.qml imports/Spectral/Component/Emoji/qmldir - imports/Spectral/Component/Timeline/AutoImage.qml - imports/Spectral/Component/Timeline/AutoLabel.qml imports/Spectral/Component/Timeline/DownloadableContent.qml imports/Spectral/Component/Timeline/GenericBubble.qml imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -20,17 +18,12 @@ imports/Spectral/Component/SideNavButton.qml imports/Spectral/Effect/ElevationEffect.qml imports/Spectral/Effect/qmldir - imports/Spectral/Form/qmldir - imports/Spectral/Form/RoomDrawer.qml - imports/Spectral/Form/RoomForm.qml - imports/Spectral/Form/RoomListForm.qml imports/Spectral/Menu/MessageContextMenu.qml imports/Spectral/Menu/qmldir imports/Spectral/Menu/RoomContextMenu.qml imports/Spectral/Page/Login.qml imports/Spectral/Page/qmldir imports/Spectral/Page/Room.qml - imports/Spectral/Page/Setting.qml assets/font/material.ttf assets/img/avatar.png assets/img/background.jpg @@ -41,5 +34,22 @@ imports/Spectral/Font/MaterialFont.qml imports/Spectral/Font/qmldir imports/Spectral/Setting/qmldir + imports/Spectral/Page/Setting.qml + imports/Spectral/Page/SettingForm.ui.qml + imports/Spectral/Page/SettingCategoryDelegate.qml + imports/Spectral/Page/SettingAccountDelegate.qml + imports/Spectral/Page/RoomForm.ui.qml + imports/Spectral/Page/LoginForm.ui.qml + imports/Spectral/Panel/qmldir + imports/Spectral/Panel/RoomDrawer.qml + imports/Spectral/Panel/RoomListPanel.qml + imports/Spectral/Panel/RoomListPanelForm.ui.qml + imports/Spectral/Panel/RoomPanel.qml + imports/Spectral/Panel/RoomPanelForm.ui.qml + imports/Spectral/Panel/RoomHeader.qml + imports/Spectral/Panel/RoomListDelegate.qml + imports/Spectral/Component/AutoImage.qml + imports/Spectral/Component/AutoLabel.qml + imports/Spectral/Component/AutoTextArea.qml diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 1a5526d..9fabd0d 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -9,6 +9,7 @@ #include #include +#include SpectralRoom::SpectralRoom(Connection* connection, QString roomId, JoinState joinState) @@ -180,5 +181,8 @@ void SpectralRoom::saveViewport(int topIndex, int bottomIndex) { void SpectralRoom::getPreviousContent(int limit) { setBusy(true); - Room::getPreviousContent(limit); + QMetaObject::invokeMethod( + this, + [=] { Room::getPreviousContent(limit); }, + Qt::QueuedConnection); }