diff --git a/assets/img/roompanel-dark.svg b/assets/img/roompanel-dark.svg
new file mode 100644
index 000000000..d240920ec
--- /dev/null
+++ b/assets/img/roompanel-dark.svg
@@ -0,0 +1,219 @@
+
+
+
+
diff --git a/imports/Spectral/Component/MaterialIcon.qml b/imports/Spectral/Component/MaterialIcon.qml
index 5f9499445..d3fa21960 100644
--- a/imports/Spectral/Component/MaterialIcon.qml
+++ b/imports/Spectral/Component/MaterialIcon.qml
@@ -1,6 +1,7 @@
import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
+import QtQuick.Controls.Material 2.2
import Spectral.Setting 0.1
import Spectral.Font 0.1
@@ -10,7 +11,7 @@ Text {
id: materialLabel
- color: MSettings.darkTheme ? "white" : "dark"
+ color: Material.foreground
font.pointSize: 16
font.family: MaterialFont.name
horizontalAlignment: Text.AlignHCenter
diff --git a/imports/Spectral/Component/SideNavButton.qml b/imports/Spectral/Component/SideNavButton.qml
deleted file mode 100644
index d2fb7e3a3..000000000
--- a/imports/Spectral/Component/SideNavButton.qml
+++ /dev/null
@@ -1,25 +0,0 @@
-import QtQuick 2.9
-import QtQuick.Controls 2.2
-import QtQuick.Layouts 1.3
-import QtQuick.Controls.Material 2.2
-
-import "qrc:/js/util.js" as Util
-
-ItemDelegate {
- property var page
- property bool selected: stackView.currentItem === page
- property color highlightColor: Material.accent
-
- Rectangle {
- width: selected ? 4 : 0
- height: parent.height
-
- color: highlightColor
-
- Behavior on width {
- PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
- }
- }
-
- onClicked: Util.pushToStack(stackView, page)
-}
diff --git a/imports/Spectral/Component/SplitView.qml b/imports/Spectral/Component/SplitView.qml
index dd43d61e9..4f176fa35 100644
--- a/imports/Spectral/Component/SplitView.qml
+++ b/imports/Spectral/Component/SplitView.qml
@@ -41,6 +41,7 @@ import QtQuick 2.9
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Window 2.1
+import Spectral.Setting 0.1
Item {
id: root
@@ -64,7 +65,7 @@ Item {
property Component handleDelegate: Rectangle {
width: 1
height: 1
- color: "#E1E1E1"
+ color: MSettings.darkTheme ? "#424242" : "#E1E1E1"
}
/*!
diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml
index f8f35d8db..e9494a5c4 100644
--- a/imports/Spectral/Component/Timeline/MessageDelegate.qml
+++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml
@@ -74,24 +74,6 @@ RowLayout {
spacing: 0
-// TimelineLabel {
-// Layout.fillWidth: true
-
-// id: authorLabel
-
-// visible: messageRow.avatarVisible
-// text: author.displayName
-// Material.foreground: Material.accent
-// coloredBackground: highlighted
-// font.bold: true
-
-// MouseArea {
-// anchors.fill: parent
-// cursorShape: Qt.PointingHandCursor
-// onClicked: roomPanelInput.insert(author.displayName)
-// }
-// }
-
TextEdit {
Layout.fillWidth: true
@@ -134,54 +116,6 @@ RowLayout {
active: eventType === "image" || eventType === "file" || eventType === "audio"
}
-
-// Row {
-// Layout.alignment: Qt.AlignRight
-
-// spacing: 4
-
-// TimelineLabel {
-// visible: userMarker.length > 5
-// text: userMarker.length - 5 + "+"
-// coloredBackground: highlighted
-// Material.foreground: "grey"
-// font.pointSize: 8
-// }
-
-// Repeater {
-// model: userMarker.length > 5 ? userMarker.slice(0, 5) : userMarker
-
-// ImageItem {
-// width: parent.height
-// height: parent.height
-
-// hint: modelData.displayName
-// source: modelData.paintable
-
-// MouseArea {
-// anchors.fill: parent
-
-// cursorShape: Qt.PointingHandCursor
-
-// onClicked: {
-// readMarkerDialog.listModel = userMarker
-// readMarkerDialog.open()
-// }
-// }
-// }
-// }
-
-// TimelineLabel {
-// id: timeLabel
-
-// visible: Math.abs(time - aboveTime) > 600000 || index == 0
-// text: Qt.formatTime(time, "hh:mm")
-// coloredBackground: highlighted
-// Material.foreground: "grey"
-// font.pointSize: 8
-// }
-// }
-
}
Component {
diff --git a/imports/Spectral/Component/Timeline/SectionDelegate.qml b/imports/Spectral/Component/Timeline/SectionDelegate.qml
index 2fe7dd43a..59d43077f 100644
--- a/imports/Spectral/Component/Timeline/SectionDelegate.qml
+++ b/imports/Spectral/Component/Timeline/SectionDelegate.qml
@@ -3,7 +3,6 @@ import QtQuick.Controls 2.2
Label {
text: section + " • " + Qt.formatTime(time, "hh:mm")
- color: "#1D333E"
font.pointSize: 9.75
font.weight: Font.Medium
font.capitalization: Font.AllUppercase
diff --git a/imports/Spectral/Effect/CircleMask.qml b/imports/Spectral/Effect/CircleMask.qml
new file mode 100644
index 000000000..fc59f408b
--- /dev/null
+++ b/imports/Spectral/Effect/CircleMask.qml
@@ -0,0 +1,29 @@
+import QtQuick 2.9
+import QtGraphicalEffects 1.0
+
+Item {
+ id: item
+
+ property alias source: mask.source
+
+ Rectangle {
+ id: circleMask
+
+ width: parent.width
+ height: parent.height
+
+ smooth: true
+ visible: false
+
+ radius: Math.max(width/2, height/2)
+ }
+
+ OpacityMask {
+ id: mask
+
+ width: parent.width
+ height: parent.height
+
+ maskSource: circleMask
+ }
+}
diff --git a/imports/Spectral/Effect/RippleEffect.qml b/imports/Spectral/Effect/RippleEffect.qml
new file mode 100644
index 000000000..970a39ce6
--- /dev/null
+++ b/imports/Spectral/Effect/RippleEffect.qml
@@ -0,0 +1,239 @@
+import QtQuick 2.9
+import QtQuick.Controls 2.2
+import QtGraphicalEffects 1.0
+
+import Spectral.Component 2.0
+import Spectral.Setting 0.1
+
+AutoMouseArea {
+ id: ripple
+
+ property color color: MSettings.darkTheme ? Qt.rgba(255, 255, 255, 0.16) : Qt.rgba(0, 0, 0, 0.08)
+ property bool circular: false
+ property bool centered: false
+ property bool focused
+ property color focusColor: "transparent"
+ property int focusWidth: width - 32
+ property Item control
+
+ clip: true
+
+ Connections {
+ target: control
+
+ onPressedChanged: {
+ if (!control.pressed)
+ __private.removeLastCircle()
+ }
+ }
+
+ onPressed: {
+ __private.createTapCircle(mouse.x, mouse.y)
+
+ if (control)
+ mouse.accepted = false
+ }
+
+ onReleased: __private.removeLastCircle()
+ onCanceled: __private.removeLastCircle()
+
+ QtObject {
+ id: __private
+
+ property int startRadius: circular ? width/10 : width/6
+ property int endRadius
+ property bool showFocus: true
+
+ property Item lastCircle
+
+ function createTapCircle(x, y) {
+ endRadius = centered ? width/2 : radius(x, y) + 5
+ showFocus = false
+
+ lastCircle = tapCircle.createObject(ripple, {
+ "circleX": centered ? width/2 : x,
+ "circleY": centered ? height/2 : y
+ })
+ }
+
+ function removeLastCircle() {
+ if (lastCircle)
+ lastCircle.removeCircle()
+ }
+
+ function radius(x, y) {
+ var dist1 = Math.max(dist(x, y, 0, 0), dist(x, y, width, height))
+ var dist2 = Math.max(dist(x, y, width, 0), dist(x, y, 0, height))
+
+ return Math.max(dist1, dist2)
+ }
+
+ function dist(x1, y1, x2, y2) {
+ var distX = x2 - x1
+ var distY = y2 - y1
+
+ return Math.sqrt(distX * distX + distY * distY)
+ }
+ }
+
+ Rectangle {
+ id: focusBackground
+ objectName: "focusBackground"
+
+ width: parent.width
+ height: parent.height
+
+ color: Qt.rgba(0,0,0,0.2)
+
+ opacity: __private.showFocus && focused ? 1 : 0
+
+ Behavior on opacity {
+ NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
+ }
+ }
+
+ Rectangle {
+ id: focusCircle
+ objectName: "focusRipple"
+
+ property bool focusedState
+
+ x: (parent.width - width)/2
+ y: (parent.height - height)/2
+
+ width: focused
+ ? focusedState ? focusWidth
+ : Math.min(parent.width - 8, focusWidth + 12)
+ : parent.width/5
+ height: width
+
+ radius: width/2
+
+ opacity: __private.showFocus && focused ? 1 : 0
+
+ color: focusColor.a === 0 ? Qt.rgba(1,1,1,0.4) : focusColor
+
+ Behavior on opacity {
+ NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
+ }
+
+ Behavior on width {
+ NumberAnimation { duration: focusTimer.interval; }
+ }
+
+ Timer {
+ id: focusTimer
+ running: focused
+ repeat: true
+ interval: 800
+
+ onTriggered: focusCircle.focusedState = !focusCircle.focusedState
+ }
+ }
+
+ Component {
+ id: tapCircle
+
+ Item {
+ id: circleItem
+ objectName: "tapRipple"
+
+ property bool done
+
+ property real circleX
+ property real circleY
+
+ property bool closed
+
+ width: parent.width
+ height: parent.height
+
+ function removeCircle() {
+ done = true
+
+ if (fillSizeAnimation.running) {
+ fillOpacityAnimation.stop()
+ closeAnimation.start()
+
+ circleItem.destroy(500);
+ } else {
+ __private.showFocus = true
+ fadeAnimation.start();
+
+ circleItem.destroy(300);
+ }
+ }
+
+ Item {
+ id: circleParent
+
+ width: parent.width
+ height: parent.height
+
+ visible: !circular
+
+ Rectangle {
+ id: circleRectangle
+
+ x: circleItem.circleX - radius
+ y: circleItem.circleY - radius
+
+ width: radius * 2
+ height: radius * 2
+
+ opacity: 0
+ color: ripple.color
+
+ NumberAnimation {
+ id: fillSizeAnimation
+ running: true
+
+ target: circleRectangle; property: "radius"; duration: 500;
+ from: __private.startRadius; to: __private.endRadius;
+ easing.type: Easing.InOutQuad
+
+ onStopped: {
+ if (done)
+ __private.showFocus = true
+ }
+ }
+
+ NumberAnimation {
+ id: fillOpacityAnimation
+ running: true
+
+ target: circleRectangle; property: "opacity"; duration: 300;
+ from: 0; to: 1; easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ id: fadeAnimation
+
+ target: circleRectangle; property: "opacity"; duration: 300;
+ from: 1; to: 0; easing.type: Easing.InOutQuad
+ }
+
+ SequentialAnimation {
+ id: closeAnimation
+
+ NumberAnimation {
+ target: circleRectangle; property: "opacity"; duration: 250;
+ to: 1; easing.type: Easing.InOutQuad
+ }
+
+ NumberAnimation {
+ target: circleRectangle; property: "opacity"; duration: 250;
+ from: 1; to: 0; easing.type: Easing.InOutQuad
+ }
+ }
+ }
+ }
+
+ CircleMask {
+ anchors.fill: parent
+ source: circleParent
+ visible: circular
+ }
+ }
+ }
+}
diff --git a/imports/Spectral/Effect/qmldir b/imports/Spectral/Effect/qmldir
index b4c470ea4..89f10e489 100644
--- a/imports/Spectral/Effect/qmldir
+++ b/imports/Spectral/Effect/qmldir
@@ -1,2 +1,3 @@
module Spectral.Effect
ElevationEffect 2.0 ElevationEffect.qml
+RippleEffect 2.0 RippleEffect.qml
diff --git a/imports/Spectral/Page/Login.qml b/imports/Spectral/Page/Login.qml
deleted file mode 100644
index fbf5a069d..000000000
--- a/imports/Spectral/Page/Login.qml
+++ /dev/null
@@ -1,27 +0,0 @@
-import QtQuick 2.9
-
-LoginForm {
- loginButton.onClicked: doLogin()
-
- Shortcut {
- sequence: "Return"
- onActivated: doLogin()
- }
-
- function doLogin() {
- if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) {
- loginButtonTooltip.text = "Server address should start with http(s)://"
- loginButtonTooltip.open()
- return
- }
-
- loginButton.text = "Logging in..."
- loginButton.enabled = false
- controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
-
- controller.connectionAdded.connect(function(conn) {
- stackView.pop()
- accountListView.currentConnection = conn
- })
- }
-}
diff --git a/imports/Spectral/Page/LoginForm.ui.qml b/imports/Spectral/Page/LoginForm.ui.qml
deleted file mode 100644
index dead32099..000000000
--- a/imports/Spectral/Page/LoginForm.ui.qml
+++ /dev/null
@@ -1,143 +0,0 @@
-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
- property alias serverField: serverField
- property alias usernameField: usernameField
- property alias passwordField: passwordField
- property alias loginButtonTooltip: loginButtonTooltip
-
- Row {
- anchors.fill: parent
-
- Rectangle {
- width: parent.width / 2
- height: parent.height
-
- color: Material.accent
-
- Column {
- x: 32
- anchors.verticalCenter: parent.verticalCenter
-
- Label {
- text: "Matrix Login."
- font.pointSize: 28
- font.bold: true
- font.capitalization: Font.AllUppercase
- color: "white"
- }
-
- Label {
- text: "A new method of messaging."
- font.pointSize: 12
- font.capitalization: Font.AllUppercase
- color: "white"
- }
- }
- }
-
- Pane {
- width: parent.width / 2
- height: parent.height
-
- padding: 64
-
- ColumnLayout {
- width: parent.width
-
- id: mainCol
-
- AutoTextField {
- 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
- }
- }
-
- AutoTextField {
- 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
- }
- }
-
- AutoTextField {
- 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
- }
- }
- }
- }
- }
-}
-
-/*##^## Designer {
- D{i:0;autoSize:true;height:480;width:640}
-}
- ##^##*/
diff --git a/imports/Spectral/Panel/RoomHeader.qml b/imports/Spectral/Panel/RoomHeader.qml
index 3b8f1d7f6..619e80ebb 100644
--- a/imports/Spectral/Panel/RoomHeader.qml
+++ b/imports/Spectral/Panel/RoomHeader.qml
@@ -6,7 +6,7 @@ import QtQuick.Controls.Material 2.2
import Spectral 0.1
import Spectral.Effect 2.0
-Rectangle {
+Control {
property alias paintable: headerImage.source
property alias topic: headerTopicLabel.text
property bool atTop: false
@@ -14,64 +14,65 @@ Rectangle {
id: header
- color: atTop ? "transparent" : "white"
+ background: Rectangle {
+ color: Material.background
- layer.enabled: !atTop
- layer.effect: ElevationEffect {
- elevation: 4
+ opacity: atTop ? 0 : 1
+
+ layer.enabled: true
+ layer.effect: ElevationEffect {
+ elevation: 2
+ }
}
- ItemDelegate {
+ RowLayout {
anchors.fill: parent
+ anchors.margins: 12
- id: roomHeader
+ spacing: 12
- onClicked: header.clicked()
+ ImageItem {
+ Layout.preferredWidth: height
+ Layout.fillHeight: true
- RowLayout {
- anchors.fill: parent
- anchors.margins: 12
+ id: headerImage
- spacing: 12
+ source: currentRoom.paintable
+ hint: currentRoom ? currentRoom.displayName : "No name"
+ }
- ImageItem {
- Layout.preferredWidth: height
- Layout.fillHeight: true
+ ColumnLayout {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
- id: headerImage
+ visible: parent.width > 64
- source: currentRoom.paintable
- hint: currentRoom ? currentRoom.displayName : "No name"
- }
-
- ColumnLayout {
+ Label {
Layout.fillWidth: true
Layout.fillHeight: true
- visible: parent.width > 64
+ text: currentRoom ? currentRoom.displayName : ""
+ font.pointSize: 12
+ elide: Text.ElideRight
+ wrapMode: Text.NoWrap
+ }
- Label {
- Layout.fillWidth: true
- Layout.fillHeight: true
+ Label {
+ Layout.fillWidth: true
+ Layout.fillHeight: true
- text: currentRoom ? currentRoom.displayName : ""
- color: "#1D333E"
- font.pointSize: 12
- elide: Text.ElideRight
- wrapMode: Text.NoWrap
- }
+ id: headerTopicLabel
- Label {
- Layout.fillWidth: true
- Layout.fillHeight: true
-
- id: headerTopicLabel
-
- color: "#5B7480"
- elide: Text.ElideRight
- wrapMode: Text.NoWrap
- }
+ color: "#5B7480"
+ elide: Text.ElideRight
+ wrapMode: Text.NoWrap
}
}
}
+
+ RippleEffect {
+ anchors.fill: parent
+
+ onClicked: header.clicked()
+ }
}
diff --git a/imports/Spectral/Panel/RoomListDelegate.qml b/imports/Spectral/Panel/RoomListDelegate.qml
deleted file mode 100644
index a342806fa..000000000
--- a/imports/Spectral/Panel/RoomListDelegate.qml
+++ /dev/null
@@ -1,96 +0,0 @@
-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
-
-Item {
- AutoMouseArea {
- anchors.fill: parent
-
- onSecondaryClicked: {
- roomContextMenu.model = model
- roomContextMenu.popup()
- }
- onPrimaryClicked: {
- if (category === RoomType.Invited) {
- inviteDialog.currentRoom = currentRoom
- inviteDialog.open()
- } else {
- leaveRoom(enteredRoom)
- enterRoom(currentRoom)
- enteredRoom = currentRoom
- }
- }
- }
-
- Rectangle {
- anchors.fill: parent
-
- visible: highlightCount > 0 || 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
-
- ImageItem {
- id: imageItem
-
- Layout.preferredWidth: height
- Layout.fillHeight: true
-
- source: paintable
- hint: name || "No Name"
- }
-
- 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"
- color: "#1D333E"
- 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,"")
- color: "#5B7480"
- font.pointSize: 9.75
- elide: Text.ElideRight
- wrapMode: Text.NoWrap
- }
- }
- }
-}
diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml
index 1858cebe0..342c59c8e 100644
--- a/imports/Spectral/Panel/RoomListPanel.qml
+++ b/imports/Spectral/Panel/RoomListPanel.qml
@@ -17,7 +17,7 @@ Rectangle {
property var controller: null
readonly property var user: controller.connection ? controller.connection.localUser : null
- readonly property int filter: 0
+ property int filter: 0
property var enteredRoom: null
property alias errorControl: errorControl
@@ -140,7 +140,7 @@ Rectangle {
background: Rectangle { color: Material.primary }
- ItemDelegate {
+ RippleEffect {
anchors.fill: parent
}
}
@@ -172,11 +172,19 @@ Rectangle {
}
}
+ ItemDelegate {
+ Layout.fillWidth: true
+
+ text: "Add Account"
+
+ onClicked: loginDialog.open()
+ }
+
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
- color: "#e7ebeb"
+ color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
}
ItemDelegate {
@@ -187,6 +195,14 @@ Rectangle {
onClicked: stackView.push(settingsPage)
}
+ ItemDelegate {
+ Layout.fillWidth: true
+
+ text: "Logout"
+
+ onClicked: controller.logout(controller.connection)
+ }
+
ItemDelegate {
Layout.fillWidth: true
@@ -249,14 +265,18 @@ Rectangle {
spacing: 0
- Control {
+ Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 64
visible: stackView.depth > 1
- contentItem: RowLayout {
+ color: Material.primary
+
+ RowLayout {
anchors.fill: parent
+ anchors.margins: 4
+
ToolButton {
Layout.preferredWidth: height
Layout.fillHeight: true
@@ -277,10 +297,6 @@ Rectangle {
elide: Label.ElideRight
}
}
-
- background: Rectangle {
- color: Material.primary
- }
}
StackView {
@@ -289,6 +305,8 @@ Rectangle {
id: stackView
+ clip: true
+
initialItem: mainPage
}
}
@@ -299,8 +317,6 @@ Rectangle {
spacing: 0
Control {
- readonly property bool isSearching: searchField.text
-
Layout.fillWidth: true
Layout.preferredHeight: 64
@@ -316,22 +332,66 @@ Rectangle {
Layout.preferredWidth: height
Layout.fillHeight: true
- contentItem: MaterialIcon {
- icon: roomListHeader.isSearching ? "\ue5cd" : "\ue8b6"
- color: roomListHeader.isSearching ? "#1D333E" : "7F7F7F"
- }
+ visible: !searchField.active
- onClicked: {
- if (searchField.focus) {
- searchField.clear()
- searchField.focus = false
- } else {
- searchField.focus = true
+ 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()
+ }
+
+ ItemDelegate {
+ 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.fillHeight: true
@@ -349,7 +409,7 @@ Rectangle {
Layout.fillHeight: true
Layout.alignment: Qt.AlignRight
- visible: !roomListHeader.isSearching
+ visible: !searchField.active
source: root.user ? root.user.paintable : null
hint: root.user ? root.user.displayName : "?"
@@ -360,6 +420,17 @@ Rectangle {
}
}
}
+
+ background: Rectangle {
+ color: Material.background
+
+ opacity: listView.atYBeginning ? 0 : 1
+
+ layer.enabled: true
+ layer.effect: ElevationEffect {
+ elevation: 2
+ }
+ }
}
Control {
@@ -423,9 +494,93 @@ Rectangle {
ScrollBar.vertical: ScrollBar {}
- delegate: RoomListDelegate {
- width: parent.width
+ delegate: Item {
+ width: listView.width
height: 64
+
+ Rectangle {
+ anchors.fill: parent
+
+ visible: highlightCount > 0 || 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
+
+ ImageItem {
+ id: imageItem
+
+ Layout.preferredWidth: height
+ Layout.fillHeight: true
+
+ source: paintable
+ hint: name || "No Name"
+ }
+
+ 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,"")
+ color: "#5B7480"
+ font.pointSize: 9.75
+ elide: Text.ElideRight
+ wrapMode: Text.NoWrap
+ }
+ }
+ }
+
+ RippleEffect {
+ anchors.fill: parent
+
+ onSecondaryClicked: {
+ roomContextMenu.model = model
+ roomContextMenu.popup()
+ }
+ onPrimaryClicked: {
+ if (category === RoomType.Invited) {
+ inviteDialog.currentRoom = currentRoom
+ inviteDialog.open()
+ } else {
+ leaveRoom(enteredRoom)
+ enterRoom(currentRoom)
+ enteredRoom = currentRoom
+ }
+ }
+ }
}
section.property: "display"
diff --git a/imports/Spectral/Panel/RoomPanelForm.ui.qml b/imports/Spectral/Panel/RoomPanelForm.ui.qml
index 5f153be66..d7236a4a4 100644
--- a/imports/Spectral/Panel/RoomPanelForm.ui.qml
+++ b/imports/Spectral/Panel/RoomPanelForm.ui.qml
@@ -54,7 +54,7 @@ Item {
visible: currentRoom
- source: "qrc:/assets/img/roompanel.svg"
+ source: MSettings.darkTheme ? "qrc:/assets/img/roompanel-dark.svg" : "qrc:/assets/img/roompanel.svg"
fillMode: Image.PreserveAspectCrop
}
diff --git a/qml/main.qml b/qml/main.qml
index 24aaeb25c..5644add1b 100644
--- a/qml/main.qml
+++ b/qml/main.qml
@@ -28,6 +28,9 @@ ApplicationWindow {
visible: true
title: qsTr("Spectral")
+ Material.foreground: Material.theme == Material.Dark ? "#FFFFFF" : "#1D333E"
+ Material.background: Material.theme == Material.Dark ? "#303030" : "#FFFFFF"
+
Platform.SystemTrayIcon {
visible: MSettings.showTray
iconSource: "qrc:/assets/img/icon.png"
@@ -52,8 +55,8 @@ ApplicationWindow {
quitOnLastWindowClosed: !MSettings.showTray
onNotificationClicked: {
- roomPage.enteredRoom = spectralController.connection.room(roomId)
- roomPage.goToEvent(eventId)
+ roomForm.enteredRoom = spectralController.connection.room(roomId)
+ roomForm.goToEvent(eventId)
showWindow()
}
onErrorOccured: {
@@ -64,6 +67,95 @@ ApplicationWindow {
onSyncDone: roomListForm.errorControl.visible = false
}
+ Dialog {
+ property bool busy: false
+
+ width: 360
+ height: 300
+ x: (window.width - width) / 2
+ y: (window.height - height) / 2
+
+ id: loginDialog
+
+ parent: ApplicationWindow.overlay
+
+ title: "Login"
+
+ contentItem: ColumnLayout {
+ AutoTextField {
+ Layout.fillWidth: true
+
+ id: serverField
+
+ placeholderText: "Server Address"
+ text: "https://matrix.org"
+ }
+
+ AutoTextField {
+ Layout.fillWidth: true
+
+ id: usernameField
+
+ placeholderText: "Username"
+ }
+
+ AutoTextField {
+ Layout.fillWidth: true
+
+ id: passwordField
+
+ placeholderText: "Password"
+ echoMode: TextInput.Password
+ }
+ }
+
+ footer: DialogButtonBox {
+ Button {
+ text: "OK"
+ flat: true
+ enabled: !loginDialog.busy
+
+ onClicked: loginDialog.doLogin()
+ }
+
+ Button {
+ text: "Cancel"
+ flat: true
+ enabled: !loginDialog.busy
+
+ onClicked: loginDialog.close()
+ }
+
+ ToolTip {
+ id: loginButtonTooltip
+ }
+ }
+
+ Component.onCompleted: {
+ spectralController.onErrorOccured.connect(function(error, detail) {
+ loginDialog.busy = false
+ loginButtonTooltip.text = error + ": " + detail
+ loginButtonTooltip.open()
+ })
+ }
+
+ function doLogin() {
+ if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) {
+ loginButtonTooltip.text = "Server address should start with http(s)://"
+ loginButtonTooltip.open()
+ return
+ }
+
+ loginDialog.busy = true
+ spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
+
+ spectralController.connectionAdded.connect(function(conn) {
+ busy = false
+ loginDialog.close()
+ })
+ }
+ }
+
SplitView {
anchors.fill: parent
@@ -108,13 +200,9 @@ ApplicationWindow {
window.hide()
}
- function showError() {
-
- }
-
-// Component.onCompleted: {
-// spectralController.initiated.connect(function() {
-// if (spectralController.accountCount == 0) stackView.push(loginPage)
-// })
-// }
+ Component.onCompleted: {
+ spectralController.initiated.connect(function() {
+ if (spectralController.accountCount == 0) loginDialog.open()
+ })
+ }
}
diff --git a/res.qrc b/res.qrc
index cdb60b50f..224dd114a 100644
--- a/res.qrc
+++ b/res.qrc
@@ -13,13 +13,11 @@
imports/Spectral/Component/AutoMouseArea.qml
imports/Spectral/Component/MaterialIcon.qml
imports/Spectral/Component/qmldir
- imports/Spectral/Component/SideNavButton.qml
imports/Spectral/Effect/ElevationEffect.qml
imports/Spectral/Effect/qmldir
imports/Spectral/Menu/MessageContextMenu.qml
imports/Spectral/Menu/qmldir
imports/Spectral/Menu/RoomContextMenu.qml
- imports/Spectral/Page/Login.qml
imports/Spectral/Page/qmldir
assets/font/material.ttf
assets/img/icon.icns
@@ -29,14 +27,12 @@
imports/Spectral/Font/MaterialFont.qml
imports/Spectral/Font/qmldir
imports/Spectral/Setting/qmldir
- imports/Spectral/Page/LoginForm.ui.qml
imports/Spectral/Panel/qmldir
imports/Spectral/Panel/RoomDrawer.qml
imports/Spectral/Panel/RoomListPanel.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/ScrollHelper.qml
imports/Spectral/Component/AutoListView.qml
imports/Spectral/Component/Timeline/TimelineImage.qml
@@ -48,5 +44,8 @@
imports/Spectral/Component/Timeline/SectionDelegate.qml
assets/img/roompanel.svg
assets/img/matrix.svg
+ imports/Spectral/Effect/RippleEffect.qml
+ imports/Spectral/Effect/CircleMask.qml
+ assets/img/roompanel-dark.svg
diff --git a/src/controller.cpp b/src/controller.cpp
index 764907d70..823081c23 100644
--- a/src/controller.cpp
+++ b/src/controller.cpp
@@ -58,26 +58,27 @@ inline QString accessTokenFileName(const AccountSettings& account) {
void Controller::loginWithCredentials(QString serverAddr, QString user,
QString pass) {
if (!user.isEmpty() && !pass.isEmpty()) {
- Connection* m_connection = new Connection(this);
- m_connection->setHomeserver(QUrl(serverAddr));
- m_connection->connectToServer(user, pass, "");
- connect(m_connection, &Connection::connected, [=] {
- AccountSettings account(m_connection->userId());
+ Connection* conn = new Connection(this);
+ conn->setHomeserver(QUrl(serverAddr));
+ conn->connectToServer(user, pass, "");
+ connect(conn, &Connection::connected, [=] {
+ AccountSettings account(conn->userId());
account.setKeepLoggedIn(true);
account.clearAccessToken(); // Drop the legacy - just in case
- account.setHomeserver(m_connection->homeserver());
- account.setDeviceId(m_connection->deviceId());
+ account.setHomeserver(conn->homeserver());
+ account.setDeviceId(conn->deviceId());
account.setDeviceName("Spectral");
- if (!saveAccessToken(account, m_connection->accessToken()))
+ if (!saveAccessToken(account, conn->accessToken()))
qWarning() << "Couldn't save access token";
account.sync();
- addConnection(m_connection);
+ addConnection(conn);
+ setConnection(conn);
});
- connect(m_connection, &Connection::networkError,
+ connect(conn, &Connection::networkError,
[=](QString error, QByteArray detail) {
emit errorOccured("Network Error", error);
});
- connect(m_connection, &Connection::loginError,
+ connect(conn, &Connection::loginError,
[=](QString error, QByteArray detail) {
emit errorOccured("Login Failed", error);
});
@@ -98,6 +99,7 @@ void Controller::logout(Connection* conn) {
conn->stopSync();
emit conn->stateChanged();
emit conn->loggedOut();
+ if (!m_connections.isEmpty()) setConnection(m_connections[0]);
});
connect(job, &LogoutJob::failure, this, [=] {
emit errorOccured("Server-side Logout Failed", job->errorString());