Move dialogs into a separate dir and create them dynamically.

Add "ignore user".
Update libqmatrixclient.
square-messages
Black Hat 2019-04-26 19:59:01 +08:00
parent 6a2f0c2105
commit 5ae1d41e92
23 changed files with 766 additions and 482 deletions

View File

@ -9,6 +9,7 @@ import Spectral 0.1
import Spectral.Setting 0.1 import Spectral.Setting 0.1
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Dialog 2.0
import Spectral.Font 0.1 import Spectral.Font 0.1
ColumnLayout { ColumnLayout {
@ -111,16 +112,19 @@ ColumnLayout {
onSecondaryClicked: messageContextMenu.popup() onSecondaryClicked: messageContextMenu.popup()
Component {
id: messageSourceDialog
MessageSourceDialog {}
}
Menu { Menu {
id: messageContextMenu id: messageContextMenu
MenuItem { MenuItem {
text: "View Source" text: "View Source"
onTriggered: { onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
sourceDialog.sourceText = toolTip
sourceDialog.open()
}
} }
MenuItem { MenuItem {
text: "Open Externally" text: "Open Externally"

View File

@ -9,6 +9,7 @@ import Spectral 0.1
import Spectral.Setting 0.1 import Spectral.Setting 0.1
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Dialog 2.0
import Spectral.Effect 2.0 import Spectral.Effect 2.0
import Spectral.Font 0.1 import Spectral.Font 0.1
@ -133,16 +134,19 @@ ColumnLayout {
onSecondaryClicked: messageContextMenu.popup() onSecondaryClicked: messageContextMenu.popup()
Component {
id: messageSourceDialog
MessageSourceDialog {}
}
Menu { Menu {
id: messageContextMenu id: messageContextMenu
MenuItem { MenuItem {
text: "View Source" text: "View Source"
onTriggered: { onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
sourceDialog.sourceText = toolTip
sourceDialog.open()
}
} }
MenuItem { MenuItem {

View File

@ -7,6 +7,7 @@ import Spectral 0.1
import Spectral.Setting 0.1 import Spectral.Setting 0.1
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Dialog 2.0
import Spectral.Effect 2.0 import Spectral.Effect 2.0
import Spectral.Font 0.1 import Spectral.Font 0.1
@ -84,6 +85,12 @@ ColumnLayout {
onSecondaryClicked: messageContextMenu.popup() onSecondaryClicked: messageContextMenu.popup()
Component {
id: messageSourceDialog
MessageSourceDialog {}
}
Menu { Menu {
readonly property string selectedText: contentLabel.selectedText readonly property string selectedText: contentLabel.selectedText
@ -92,10 +99,7 @@ ColumnLayout {
MenuItem { MenuItem {
text: "View Source" text: "View Source"
onTriggered: { onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
sourceDialog.sourceText = toolTip
sourceDialog.open()
}
} }
MenuItem { MenuItem {
text: "Reply" text: "Reply"

View File

@ -0,0 +1,38 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
Dialog {
anchors.centerIn: parent
width: 360
id: root
title: "Create a Room"
contentItem: ColumnLayout {
AutoTextField {
Layout.fillWidth: true
id: roomNameField
placeholderText: "Room Name"
}
AutoTextField {
Layout.fillWidth: true
id: roomTopicField
placeholderText: "Room Topic"
}
}
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: spectralController.createRoom(spectralController.connection, roomNameField.text, roomTopicField.text)
onClosed: destroy()
}

View File

@ -0,0 +1,28 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
Dialog {
property var room
anchors.centerIn: parent
width: 360
id: root
title: "Invite User"
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
contentItem: AutoTextField {
id: inviteUserDialogTextField
placeholderText: "User ID"
}
onAccepted: room.inviteToRoom(inviteUserDialogTextField.text)
onClosed: destroy()
}

View File

@ -0,0 +1,38 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
Dialog {
anchors.centerIn: parent
width: 360
id: root
title: "Start a Chat"
contentItem: ColumnLayout {
AutoTextField {
Layout.fillWidth: true
id: identifierField
placeholderText: "Room Alias/User ID"
}
}
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: {
var identifier = identifierField.text
var firstChar = identifier.charAt(0)
if (firstChar == "@") {
spectralController.createDirectChat(spectralController.connection, identifier)
} else if (firstChar == "!" || firstChar == "#") {
spectralController.joinRoom(spectralController.connection, identifier)
}
}
onClosed: destroy()
}

View File

@ -0,0 +1,56 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
Dialog {
anchors.centerIn: parent
width: 360
id: root
title: "Login"
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: doLogin()
contentItem: ColumnLayout {
AutoTextField {
Layout.fillWidth: true
id: serverField
placeholderText: "Server Address"
text: "https://matrix.org"
}
AutoTextField {
Layout.fillWidth: true
id: usernameField
placeholderText: "Username"
onAccepted: passwordField.forceActiveFocus()
}
AutoTextField {
Layout.fillWidth: true
id: passwordField
placeholderText: "Password"
echoMode: TextInput.Password
onAccepted: root.doLogin()
}
}
function doLogin() {
spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
}
onClosed: root.destroy()
}

View File

@ -0,0 +1,27 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
Popup {
property string sourceText
anchors.centerIn: parent
width: 480
id: root
modal: true
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: ScrollView {
clip: true
Label {
text: sourceText
}
}
onClosed: root.destroy()
}

View File

@ -0,0 +1,218 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
import Spectral.Effect 2.0
import Spectral.Setting 0.1
Dialog {
property var room
anchors.centerIn: parent
width: 480
id: root
title: "Room Settings - " + (room ? room.displayName : "")
modal: true
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: 16
Avatar {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
Layout.alignment: Qt.AlignTop
hint: room ? room.displayName : "No name"
source: room ? room.avatarMediaId : null
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: 4
AutoTextField {
Layout.fillWidth: true
text: room ? room.name : ""
placeholderText: "Room Name"
}
AutoTextField {
Layout.fillWidth: true
text: room ? room.topic : ""
placeholderText: "Room Topic"
}
}
}
Control {
Layout.fillWidth: true
visible: room ? room.predecessorId : false
padding: 8
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
icon: "\ue8d4"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Label {
Layout.fillWidth: true
font.bold: true
color: MPalette.foreground
text: "This room is a continuation of another conversation."
}
Label {
Layout.fillWidth: true
color: MPalette.lighter
text: "Click here to see older messages."
}
}
}
background: Rectangle {
color: MPalette.banner
RippleEffect {
anchors.fill: parent
onClicked: {
roomListForm.enteredRoom = spectralController.connection.room(room.predecessorId)
root.close()
}
}
}
}
Control {
Layout.fillWidth: true
visible: room ? room.successorId : false
padding: 8
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
icon: "\ue8d4"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Label {
Layout.fillWidth: true
font.bold: true
color: MPalette.foreground
text: "This room has been replaced and is no longer active."
}
Label {
Layout.fillWidth: true
color: MPalette.lighter
text: "The conversation continues here."
}
}
}
background: Rectangle {
color: MPalette.banner
RippleEffect {
anchors.fill: parent
onClicked: {
roomListForm.enteredRoom = spectralController.connection.room(room.successorId)
root.close()
}
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
wrapMode: Label.Wrap
text: "Main Alias"
color: MPalette.lighter
}
ComboBox {
Layout.fillWidth: true
model: room ? room.aliases : null
currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1
}
}
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignTop
wrapMode: Label.Wrap
text: "Aliases"
color: MPalette.lighter
}
ColumnLayout {
Layout.fillWidth: true
Repeater {
model: room ? room.aliases : null
delegate: Label {
Layout.fillWidth: true
text: modelData
font.pixelSize: 12
color: MPalette.lighter
}
}
}
}
}
}
onClosed: destroy()
}

View File

@ -0,0 +1,162 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Spectral.Component 2.0
import Spectral.Effect 2.0
import Spectral.Setting 0.1
Dialog {
property var room
property var user
anchors.centerIn: parent
width: 360
id: root
modal: true
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: 16
Avatar {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
hint: user ? user.displayName : "No name"
source: user ? user.avatarMediaId : null
}
ColumnLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
font.pixelSize: 18
font.bold: true
wrapMode: Label.Wrap
text: user ? user.displayName : "No Name"
color: MPalette.foreground
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Online"
color: MPalette.lighter
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
RowLayout {
Layout.fillWidth: true
spacing: 8
MaterialIcon {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
icon: "\ue88f"
color: MPalette.lighter
}
ColumnLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: user ? user.id : "No ID"
color: MPalette.accent
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "User ID"
color: MPalette.lighter
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
Control {
Layout.fillWidth: true
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
icon: room.connection.isIgnored(user) ? "\ue7f5" : "\ue7f6"
color: MPalette.lighter
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: room.connection.isIgnored(user) ? "Unignore this user" : "Ignore this user"
color: MPalette.accent
}
}
background: RippleEffect {
onPrimaryClicked: {
room.connection.isIgnored(user) ? room.connection.removeFromIgnoredUsers(user) : room.connection.addToIgnoredUsers(user)
root.close()
}
}
}
Control {
Layout.fillWidth: true
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
Layout.alignment: Qt.AlignTop
icon: "\ue5d9"
color: MPalette.lighter
}
Label {
Layout.fillWidth: true
wrapMode: Label.Wrap
text: "Kick this user"
color: MPalette.accent
}
}
background: RippleEffect {
onPrimaryClicked: room.kickMember(user.id)
}
}
}
onClosed: destroy()
}

View File

@ -0,0 +1,8 @@
module Spectral.Dialog
RoomSettingsDialog 2.0 RoomSettingsDialog.qml
UserDetailDialog 2.0 UserDetailDialog.qml
MessageSourceDialog 2.0 MessageSourceDialog.qml
LoginDialog 2.0 LoginDialog.qml
CreateRoomDialog 2.0 CreateRoomDialog.qml
JoinRoomDialog 2.0 JoinRoomDialog.qml
InviteUserDialog 2.0 InviteUserDialog.qml

View File

@ -4,6 +4,7 @@ import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Dialog 2.0
import Spectral.Effect 2.0 import Spectral.Effect 2.0
import Spectral.Setting 0.1 import Spectral.Setting 0.1
@ -115,7 +116,7 @@ Drawer {
} }
background: RippleEffect { background: RippleEffect {
onPrimaryClicked: roomDetailDialog.open() onPrimaryClicked: roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
} }
} }
@ -153,7 +154,7 @@ Drawer {
color: MPalette.lighter color: MPalette.lighter
} }
onClicked: inviteUserDialog.open() onClicked: inviteUserDialog.createObject(ApplicationWindow.overlay, {"room": room}).open()
} }
} }
@ -198,6 +199,8 @@ Drawer {
RippleEffect { RippleEffect {
anchors.fill: parent anchors.fill: parent
onPrimaryClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user}).open()
} }
} }
@ -205,225 +208,21 @@ Drawer {
} }
} }
Dialog { Component {
anchors.centerIn: parent id: roomSettingDialog
width: 360
RoomSettingsDialog {}
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
Component {
id: inviteUserDialog id: inviteUserDialog
parent: ApplicationWindow.overlay InviteUserDialog {}
title: "Invite User"
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
contentItem: AutoTextField {
id: inviteUserDialogTextField
placeholderText: "User ID"
}
onAccepted: room.inviteToRoom(inviteUserDialogTextField.text)
}
Dialog {
anchors.centerIn: parent
width: 480
id: roomDetailDialog
parent: ApplicationWindow.overlay
title: "Room Settings - " + (room ? room.displayName : "")
modal: true
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
spacing: 16
Avatar {
Layout.preferredWidth: 72
Layout.preferredHeight: 72
Layout.alignment: Qt.AlignTop
hint: room ? room.displayName : "No name"
source: room ? room.avatarMediaId : null
}
ColumnLayout {
Layout.fillWidth: true
Layout.margins: 4
AutoTextField {
Layout.fillWidth: true
text: room ? room.name : ""
placeholderText: "Room Name"
}
AutoTextField {
Layout.fillWidth: true
text: room ? room.topic : ""
placeholderText: "Room Topic"
}
}
}
Control {
Layout.fillWidth: true
visible: room ? room.predecessorId : false
padding: 8
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
icon: "\ue8d4"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Label {
Layout.fillWidth: true
font.bold: true
color: MPalette.foreground
text: "This room is a continuation of another conversation."
}
Label {
Layout.fillWidth: true
color: MPalette.lighter
text: "Click here to see older messages."
}
}
}
background: Rectangle {
color: MPalette.banner
RippleEffect {
anchors.fill: parent
onClicked: roomListForm.enteredRoom = spectralController.connection.room(room.predecessorId)
}
}
}
Control {
Layout.fillWidth: true
visible: room ? room.successorId : false
padding: 8
contentItem: RowLayout {
MaterialIcon {
Layout.preferredWidth: 48
Layout.preferredHeight: 48
icon: "\ue8d4"
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Label {
Layout.fillWidth: true
font.bold: true
color: MPalette.foreground
text: "This room has been replaced and is no longer active."
}
Label {
Layout.fillWidth: true
color: MPalette.lighter
text: "The conversation continues here."
}
}
}
background: Rectangle {
color: MPalette.banner
RippleEffect {
anchors.fill: parent
onClicked: roomListForm.enteredRoom = spectralController.connection.room(room.successorId)
}
}
}
MenuSeparator {
Layout.fillWidth: true
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
wrapMode: Label.Wrap
text: "Main Alias"
color: MPalette.lighter
}
ComboBox {
Layout.fillWidth: true
model: room ? room.aliases : null
currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1
}
}
RowLayout {
Layout.fillWidth: true
Label {
Layout.preferredWidth: 100
Layout.alignment: Qt.AlignTop
wrapMode: Label.Wrap
text: "Aliases"
color: MPalette.lighter
}
ColumnLayout {
Layout.fillWidth: true
Repeater {
model: room ? room.aliases : null
delegate: Label {
Layout.fillWidth: true
text: modelData
font.pixelSize: 12
color: MPalette.lighter
}
}
}
}
}
}
} }
} }

View File

@ -88,9 +88,11 @@ Item {
sourceModel: messageEventModel sourceModel: messageEventModel
filters: ExpressionFilter { filters: [
expression: marks !== 0x10 && eventType !== "other" ExpressionFilter {
} expression: marks !== 0x10 && eventType !== "other"
}
]
onModelReset: { onModelReset: {
if (currentRoom) { if (currentRoom) {
@ -272,31 +274,6 @@ Item {
onClicked: messageListView.positionViewAtBeginning() onClicked: messageListView.positionViewAtBeginning()
} }
Popup {
property string sourceText
anchors.centerIn: parent
width: 480
id: sourceDialog
parent: ApplicationWindow.overlay
padding: 16
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
contentItem: ScrollView {
clip: true
TextArea {
readOnly: true
selectByMouse: true
text: sourceDialog.sourceText
}
}
}
} }
Control { Control {

@ -1 +1 @@
Subproject commit af55d9a0f23ed48c7dcec26efe6b01fb44a8c4fc Subproject commit 52a81dfa8a5415be369d819837f445479b833cde

View File

@ -7,6 +7,7 @@ import Qt.labs.platform 1.0 as Platform
import Spectral.Panel 2.0 import Spectral.Panel 2.0
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Dialog 2.0
import Spectral.Page 2.0 import Spectral.Page 2.0
import Spectral.Effect 2.0 import Spectral.Effect 2.0
@ -39,16 +40,14 @@ ApplicationWindow {
menu: Platform.Menu { menu: Platform.Menu {
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Hide Window") text: qsTr("Toggle Window")
onTriggered: hideWindow() onTriggered: window.visible ? hideWindow() : showWindow()
} }
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Quit") text: qsTr("Quit")
onTriggered: Qt.quit() onTriggered: Qt.quit()
} }
} }
onActivated: showWindow()
} }
Controller { Controller {
@ -74,102 +73,6 @@ ApplicationWindow {
onActivated: Qt.quit() onActivated: Qt.quit()
} }
Dialog {
property bool busy: false
width: 360
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"
onAccepted: passwordField.forceActiveFocus()
}
AutoTextField {
Layout.fillWidth: true
id: passwordField
placeholderText: "Password"
echoMode: TextInput.Password
onAccepted: loginDialog.doLogin()
}
}
footer: DialogButtonBox {
Button {
text: "Cancel"
flat: true
enabled: !loginDialog.busy
onClicked: loginDialog.close()
}
Button {
text: "OK"
flat: true
enabled: !loginDialog.busy
onClicked: loginDialog.doLogin()
}
ToolTip {
id: loginButtonTooltip
}
}
onVisibleChanged: {
if (visible) spectralController.onErrorOccured.connect(showError)
else spectralController.onErrorOccured.disconnect(showError)
}
function showError(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()
})
}
}
Dialog { Dialog {
anchors.centerIn: parent anchors.centerIn: parent
@ -267,7 +170,7 @@ ApplicationWindow {
color: MPalette.lighter color: MPalette.lighter
} }
onClicked: loginDialog.open() onClicked: loginDialog.createObject(ApplicationWindow.overlay).open()
} }
} }
@ -294,7 +197,7 @@ ApplicationWindow {
RippleEffect { RippleEffect {
anchors.fill: parent anchors.fill: parent
onPrimaryClicked: joinRoomDialog.open() onPrimaryClicked: joinRoomDialog.createObject(ApplicationWindow.overlay).open()
} }
} }
@ -321,7 +224,7 @@ ApplicationWindow {
RippleEffect { RippleEffect {
anchors.fill: parent anchors.fill: parent
onPrimaryClicked: createRoomDialog.open() onPrimaryClicked: createRoomDialog.createObject(ApplicationWindow.overlay).open()
} }
} }
@ -433,66 +336,22 @@ ApplicationWindow {
} }
} }
Dialog { Component {
anchors.centerIn: parent id: loginDialog
width: 360
id: joinRoomDialog LoginDialog {}
title: "Start a Chat"
contentItem: ColumnLayout {
AutoTextField {
Layout.fillWidth: true
id: identifierField
placeholderText: "Room Alias/User ID"
}
}
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: {
var identifier = identifierField.text
var firstChar = identifier.charAt(0)
if (firstChar == "@") {
spectralController.createDirectChat(spectralController.connection, identifier)
} else if (firstChar == "!" || firstChar == "#") {
spectralController.joinRoom(spectralController.connection, identifier)
}
}
} }
Dialog { Component {
anchors.centerIn: parent id: joinRoomDialog
width: 360
JoinRoomDialog {}
}
Component {
id: createRoomDialog id: createRoomDialog
title: "Create a Room" CreateRoomDialog {}
contentItem: ColumnLayout {
AutoTextField {
Layout.fillWidth: true
id: roomNameField
placeholderText: "Room Name"
}
AutoTextField {
Layout.fillWidth: true
id: roomTopicField
placeholderText: "Room Topic"
}
}
standardButtons: Dialog.Ok | Dialog.Cancel
onAccepted: spectralController.createRoom(spectralController.connection, roomNameField.text, roomTopicField.text)
} }
Drawer { Drawer {

View File

@ -44,5 +44,13 @@
<file>imports/Spectral/Setting/Palette.qml</file> <file>imports/Spectral/Setting/Palette.qml</file>
<file>imports/Spectral/Component/Timeline/FileDelegate.qml</file> <file>imports/Spectral/Component/Timeline/FileDelegate.qml</file>
<file>imports/Spectral/Component/FullScreenImage.qml</file> <file>imports/Spectral/Component/FullScreenImage.qml</file>
<file>imports/Spectral/Dialog/qmldir</file>
<file>imports/Spectral/Dialog/RoomSettingsDialog.qml</file>
<file>imports/Spectral/Dialog/UserDetailDialog.qml</file>
<file>imports/Spectral/Dialog/MessageSourceDialog.qml</file>
<file>imports/Spectral/Dialog/LoginDialog.qml</file>
<file>imports/Spectral/Dialog/CreateRoomDialog.qml</file>
<file>imports/Spectral/Dialog/JoinRoomDialog.qml</file>
<file>imports/Spectral/Dialog/InviteUserDialog.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -7,6 +7,7 @@
#include "events/eventcontent.h" #include "events/eventcontent.h"
#include "events/roommessageevent.h" #include "events/roommessageevent.h"
#include "csapi/account-data.h"
#include "csapi/joining.h" #include "csapi/joining.h"
#include "csapi/logout.h" #include "csapi/logout.h"

View File

@ -3,9 +3,9 @@
#include "connection.h" #include "connection.h"
#include "notifications/manager.h" #include "notifications/manager.h"
#include "room.h"
#include "settings.h" #include "settings.h"
#include "user.h" #include "user.h"
#include "room.h"
#include <QApplication> #include <QApplication>
#include <QMediaPlayer> #include <QMediaPlayer>
@ -54,13 +54,16 @@ class Controller : public QObject {
} }
Connection* connection() { Connection* connection() {
if (m_connection.isNull()) return nullptr; if (m_connection.isNull())
return nullptr;
return m_connection; return m_connection;
} }
void setConnection(Connection* conn) { void setConnection(Connection* conn) {
if (!conn) return; if (!conn)
if (conn == m_connection) return; return;
if (conn == m_connection)
return;
m_connection = conn; m_connection = conn;
emit connectionChanged(); emit connectionChanged();
} }
@ -100,9 +103,12 @@ class Controller : public QObject {
void createDirectChat(Connection* c, const QString& userID); void createDirectChat(Connection* c, const QString& userID);
void copyToClipboard(const QString& text); void copyToClipboard(const QString& text);
void playAudio(QUrl localFile); void playAudio(QUrl localFile);
void postNotification(const QString& roomId, const QString& eventId, void postNotification(const QString& roomId,
const QString& roomName, const QString& senderName, const QString& eventId,
const QString& text, const QImage& icon); const QString& roomName,
const QString& senderName,
const QString& text,
const QImage& icon);
}; };
#endif // CONTROLLER_H #endif // CONTROLLER_H

View File

@ -23,7 +23,7 @@
using namespace QMatrixClient; using namespace QMatrixClient;
int main(int argc, char *argv[]) { int main(int argc, char* argv[]) {
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD) #if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
if (qgetenv("QT_SCALE_FACTOR").size() == 0) { if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
QSettings settings("ENCOM", "Spectral"); QSettings settings("ENCOM", "Spectral");
@ -59,11 +59,13 @@ int main(int argc, char *argv[]) {
"RoomMessageEvent", "ENUM"); "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM"); qmlRegisterUncreatableType<RoomType>("Spectral", 0, 1, "RoomType", "ENUM");
qRegisterMetaType<User *>("User*"); qRegisterMetaType<User*>("User*");
qRegisterMetaType<Room *>("Room*"); qRegisterMetaType<User*>("const User*");
qRegisterMetaType<Room*>("Room*");
qRegisterMetaType<Connection*>("Connection*");
qRegisterMetaType<MessageEventType>("MessageEventType"); qRegisterMetaType<MessageEventType>("MessageEventType");
qRegisterMetaType<SpectralRoom *>("SpectralRoom*"); qRegisterMetaType<SpectralRoom*>("SpectralRoom*");
qRegisterMetaType<SpectralUser *>("SpectralUser*"); qRegisterMetaType<SpectralUser*>("SpectralUser*");
#if defined(BUNDLE_FONT) #if defined(BUNDLE_FONT)
QFontDatabase::addApplicationFont(":/assets/font/roboto.ttf"); QFontDatabase::addApplicationFont(":/assets/font/roboto.ttf");
@ -73,12 +75,13 @@ int main(int argc, char *argv[]) {
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
engine.addImportPath("qrc:/imports"); engine.addImportPath("qrc:/imports");
ImageProvider *m_provider = new ImageProvider(); ImageProvider* m_provider = new ImageProvider();
engine.rootContext()->setContextProperty("imageProvider", m_provider); engine.rootContext()->setContextProperty("imageProvider", m_provider);
engine.addImageProvider(QLatin1String("mxc"), m_provider); engine.addImageProvider(QLatin1String("mxc"), m_provider);
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
if (engine.rootObjects().isEmpty()) return -1; if (engine.rootObjects().isEmpty())
return -1;
return app.exec(); return app.exec();
} }

View File

@ -41,7 +41,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
return roles; return roles;
} }
MessageEventModel::MessageEventModel(QObject *parent) MessageEventModel::MessageEventModel(QObject* parent)
: QAbstractListModel(parent), m_currentRoom(nullptr) { : QAbstractListModel(parent), m_currentRoom(nullptr) {
using namespace QMatrixClient; using namespace QMatrixClient;
qmlRegisterType<FileTransferInfo>(); qmlRegisterType<FileTransferInfo>();
@ -52,8 +52,9 @@ MessageEventModel::MessageEventModel(QObject *parent)
MessageEventModel::~MessageEventModel() {} MessageEventModel::~MessageEventModel() {}
void MessageEventModel::setRoom(SpectralRoom *room) { void MessageEventModel::setRoom(SpectralRoom* room) {
if (room == m_currentRoom) return; if (room == m_currentRoom)
return;
beginResetModel(); beginResetModel();
if (m_currentRoom) { if (m_currentRoom) {
@ -96,8 +97,9 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
connect(m_currentRoom, &Room::pendingEventAdded, this, connect(m_currentRoom, &Room::pendingEventAdded, this,
&MessageEventModel::endInsertRows); &MessageEventModel::endInsertRows);
connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, connect(m_currentRoom, &Room::pendingEventAboutToMerge, this,
[this](RoomEvent *, int i) { [this](RoomEvent*, int i) {
if (i == 0) return; // No need to move anything, just refresh if (i == 0)
return; // No need to move anything, just refresh
movingEvent = true; movingEvent = true;
// Reverse i because row 0 is bottommost in the model // Reverse i because row 0 is bottommost in the model
@ -131,7 +133,7 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
refreshEventRoles(lastReadEventId, {ReadMarkerRole}); refreshEventRoles(lastReadEventId, {ReadMarkerRole});
}); });
connect(m_currentRoom, &Room::replacedEvent, this, connect(m_currentRoom, &Room::replacedEvent, this,
[this](const RoomEvent *newEvent) { [this](const RoomEvent* newEvent) {
refreshLastUserEvents(refreshEvent(newEvent->id()) - refreshLastUserEvents(refreshEvent(newEvent->id()) -
timelineBaseIndex()); timelineBaseIndex());
}); });
@ -144,10 +146,15 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
connect(m_currentRoom, &Room::fileTransferCancelled, this, connect(m_currentRoom, &Room::fileTransferCancelled, this,
&MessageEventModel::refreshEvent); &MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::readMarkerForUserMoved, this, connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
[=](User *, QString fromEventId, QString toEventId) { [=](User*, QString fromEventId, QString toEventId) {
refreshEventRoles(fromEventId, {UserMarkerRole}); refreshEventRoles(fromEventId, {UserMarkerRole});
refreshEventRoles(toEventId, {UserMarkerRole}); refreshEventRoles(toEventId, {UserMarkerRole});
}); });
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged,
this, [=] {
beginResetModel();
endResetModel();
});
qDebug() << "Connected to room" << room->id() << "as" qDebug() << "Connected to room" << room->id() << "as"
<< room->localUser()->id(); << room->localUser()->id();
} else } else
@ -155,23 +162,25 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
endResetModel(); endResetModel();
} }
int MessageEventModel::refreshEvent(const QString &eventId) { int MessageEventModel::refreshEvent(const QString& eventId) {
return refreshEventRoles(eventId); return refreshEventRoles(eventId);
} }
void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); } void MessageEventModel::refreshRow(int row) {
refreshEventRoles(row);
}
int MessageEventModel::timelineBaseIndex() const { int MessageEventModel::timelineBaseIndex() const {
return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0;
} }
void MessageEventModel::refreshEventRoles(int row, const QVector<int> &roles) { void MessageEventModel::refreshEventRoles(int row, const QVector<int>& roles) {
const auto idx = index(row); const auto idx = index(row);
emit dataChanged(idx, idx, roles); emit dataChanged(idx, idx, roles);
} }
int MessageEventModel::refreshEventRoles(const QString &eventId, int MessageEventModel::refreshEventRoles(const QString& eventId,
const QVector<int> &roles) { const QVector<int>& roles) {
const auto it = m_currentRoom->findInTimeline(eventId); const auto it = m_currentRoom->findInTimeline(eventId);
if (it == m_currentRoom->timelineEdge()) { if (it == m_currentRoom->timelineEdge()) {
qWarning() << "Trying to refresh inexistent event:" << eventId; qWarning() << "Trying to refresh inexistent event:" << eventId;
@ -183,15 +192,16 @@ int MessageEventModel::refreshEventRoles(const QString &eventId,
return row; return row;
} }
inline bool hasValidTimestamp(const QMatrixClient::TimelineItem &ti) { inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) {
return ti->timestamp().isValid(); return ti->timestamp().isValid();
} }
QDateTime MessageEventModel::makeMessageTimestamp( QDateTime MessageEventModel::makeMessageTimestamp(
const QMatrixClient::Room::rev_iter_t &baseIt) const { const QMatrixClient::Room::rev_iter_t& baseIt) const {
const auto &timeline = m_currentRoom->messageEvents(); const auto& timeline = m_currentRoom->messageEvents();
auto ts = baseIt->event()->timestamp(); auto ts = baseIt->event()->timestamp();
if (ts.isValid()) return ts; if (ts.isValid())
return ts;
// The event is most likely redacted or just invalid. // The event is most likely redacted or just invalid.
// Look for the nearest date around and slap zero time to it. // Look for the nearest date around and slap zero time to it.
@ -210,11 +220,14 @@ QDateTime MessageEventModel::makeMessageTimestamp(
QString MessageEventModel::renderDate(QDateTime timestamp) const { QString MessageEventModel::renderDate(QDateTime timestamp) const {
auto date = timestamp.toLocalTime().date(); auto date = timestamp.toLocalTime().date();
if (date == QDate::currentDate()) return tr("Today"); if (date == QDate::currentDate())
if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday"); return tr("Today");
if (date == QDate::currentDate().addDays(-1))
return tr("Yesterday");
if (date == QDate::currentDate().addDays(-2)) if (date == QDate::currentDate().addDays(-2))
return tr("The day before yesterday"); return tr("The day before yesterday");
if (date > QDate::currentDate().addDays(-7)) return date.toString("dddd"); if (date > QDate::currentDate().addDays(-7))
return date.toString("dddd");
return date.toString(Qt::DefaultLocaleShortDate); return date.toString(Qt::DefaultLocaleShortDate);
} }
@ -222,8 +235,8 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
return; return;
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin(); const auto& timelineBottom = m_currentRoom->messageEvents().rbegin();
const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
const auto limit = timelineBottom + std::min(baseTimelineRow + 10, const auto limit = timelineBottom + std::min(baseTimelineRow + 10,
m_currentRoom->timelineSize()); m_currentRoom->timelineSize());
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
@ -235,12 +248,13 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
} }
} }
int MessageEventModel::rowCount(const QModelIndex &parent) const { int MessageEventModel::rowCount(const QModelIndex& parent) const {
if (!m_currentRoom || parent.isValid()) return 0; if (!m_currentRoom || parent.isValid())
return 0;
return m_currentRoom->timelineSize(); return m_currentRoom->timelineSize();
} }
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
const auto row = idx.row(); const auto row = idx.row();
if (!m_currentRoom || row < 0 || if (!m_currentRoom || row < 0 ||
@ -253,7 +267,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
std::max(0, row - timelineBaseIndex()); std::max(0, row - timelineBaseIndex());
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + const auto pendingIt = m_currentRoom->pendingEvents().crbegin() +
std::min(row, timelineBaseIndex()); std::min(row, timelineBaseIndex());
const auto &evt = isPending ? **pendingIt : **timelineIt; const auto& evt = isPending ? **pendingIt : **timelineIt;
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
return utils::removeReply(m_currentRoom->eventToString(evt, Qt::RichText)); return utils::removeReply(m_currentRoom->eventToString(evt, Qt::RichText));
@ -282,7 +296,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
return e->hasFileContent() ? "file" : "message"; return e->hasFileContent() ? "file" : "message";
} }
} }
if (evt.isStateEvent()) return "state"; if (evt.isStateEvent())
return "state";
return "other"; return "other";
} }
@ -298,7 +313,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
if (role == ContentTypeRole) { if (role == ContentTypeRole) {
if (auto e = eventCast<const RoomMessageEvent>(&evt)) { if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
const auto &contentType = e->mimeType().name(); const auto& contentType = e->mimeType().name();
return contentType == "text/plain" ? QStringLiteral("text/html") return contentType == "text/plain" ? QStringLiteral("text/html")
: contentType; : contentType;
} }
@ -323,19 +338,27 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
}; };
} }
if (role == HighlightRole) return m_currentRoom->isEventHighlighted(&evt); if (role == HighlightRole)
return m_currentRoom->isEventHighlighted(&evt);
if (role == ReadMarkerRole) if (role == ReadMarkerRole)
return evt.id() == lastReadEventId && row > timelineBaseIndex(); return evt.id() == lastReadEventId && row > timelineBaseIndex();
if (role == SpecialMarksRole) { if (role == SpecialMarksRole) {
if (isPending) return pendingIt->deliveryStatus(); if (isPending)
return pendingIt->deliveryStatus();
if (is<RedactionEvent>(evt)) return EventStatus::Hidden; if (is<RedactionEvent>(evt))
if (evt.isRedacted()) return EventStatus::Hidden; return EventStatus::Hidden;
if (evt.isRedacted())
return EventStatus::Hidden;
if (evt.isStateEvent() && if (evt.isStateEvent() &&
static_cast<const StateEventBase &>(evt).repeatsState()) static_cast<const StateEventBase&>(evt).repeatsState())
return EventStatus::Hidden;
if (m_currentRoom->connection()->isIgnored(
m_currentRoom->user(evt.senderId())))
return EventStatus::Hidden; return EventStatus::Hidden;
return EventStatus::Normal; return EventStatus::Normal;
@ -351,7 +374,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
} }
if (role == AnnotationRole) if (role == AnnotationRole)
if (isPending) return pendingIt->annotation(); if (isPending)
return pendingIt->annotation();
if (role == TimeRole || role == SectionRole) { if (role == TimeRole || role == SectionRole) {
auto ts = auto ts =
@ -361,8 +385,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
if (role == UserMarkerRole) { if (role == UserMarkerRole) {
QVariantList variantList; QVariantList variantList;
for (User *user : m_currentRoom->usersAtEventId(evt.id())) { for (User* user : m_currentRoom->usersAtEventId(evt.id())) {
if (user == m_currentRoom->localUser()) continue; if (user == m_currentRoom->localUser())
continue;
variantList.append(QVariant::fromValue(user)); variantList.append(QVariant::fromValue(user));
} }
return variantList; return variantList;
@ -370,22 +395,24 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
if (role == ReplyEventIdRole || role == ReplyDisplayRole || if (role == ReplyEventIdRole || role == ReplyDisplayRole ||
role == ReplyAuthorRole) { role == ReplyAuthorRole) {
const QString &replyEventId = evt.contentJson()["m.relates_to"] const QString& replyEventId = evt.contentJson()["m.relates_to"]
.toObject()["m.in_reply_to"] .toObject()["m.in_reply_to"]
.toObject()["event_id"] .toObject()["event_id"]
.toString(); .toString();
if (replyEventId.isEmpty()) return {}; if (replyEventId.isEmpty())
return {};
const auto replyIt = m_currentRoom->findInTimeline(replyEventId); const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
if (replyIt == m_currentRoom->timelineEdge()) return {}; if (replyIt == m_currentRoom->timelineEdge())
return {};
const auto& replyEvt = **replyIt; const auto& replyEvt = **replyIt;
switch (role) { switch (role) {
case ReplyEventIdRole: case ReplyEventIdRole:
return replyEventId; return replyEventId;
case ReplyDisplayRole: case ReplyDisplayRole:
return utils::removeReply(m_currentRoom->eventToString(replyEvt, Qt::RichText)); return utils::removeReply(
m_currentRoom->eventToString(replyEvt, Qt::RichText));
case ReplyAuthorRole: case ReplyAuthorRole:
return QVariant::fromValue( return QVariant::fromValue(m_currentRoom->user(replyEvt.senderId()));
m_currentRoom->user(replyEvt.senderId()));
} }
return {}; return {};
} }
@ -394,7 +421,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
role == AboveAuthorRole || role == AboveTimeRole) role == AboveAuthorRole || role == AboveTimeRole)
for (auto r = row + 1; r < rowCount(); ++r) { for (auto r = row + 1; r < rowCount(); ++r) {
auto i = index(r); auto i = index(r);
if (data(i, SpecialMarksRole) != EventStatus::Hidden) switch (role) { if (data(i, SpecialMarksRole) != EventStatus::Hidden)
switch (role) {
case AboveEventTypeRole: case AboveEventTypeRole:
return data(i, EventTypeRole); return data(i, EventTypeRole);
case AboveSectionRole: case AboveSectionRole:
@ -409,7 +437,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
return {}; return {};
} }
int MessageEventModel::eventIDToIndex(const QString &eventID) { int MessageEventModel::eventIDToIndex(const QString& eventID) {
const auto it = m_currentRoom->findInTimeline(eventID); const auto it = m_currentRoom->findInTimeline(eventID);
if (it == m_currentRoom->timelineEdge()) { if (it == m_currentRoom->timelineEdge()) {
qWarning() << "Trying to find inexistent event:" << eventID; qWarning() << "Trying to find inexistent event:" << eventID;

View File

@ -6,6 +6,7 @@
#include "csapi/content-repo.h" #include "csapi/content-repo.h"
#include "csapi/leaving.h" #include "csapi/leaving.h"
#include "csapi/typing.h" #include "csapi/typing.h"
#include "events/accountdataevents.h"
#include "events/typingevent.h" #include "events/typingevent.h"
#include <QFileDialog> #include <QFileDialog>

View File

@ -14,14 +14,16 @@ UserListModel::UserListModel(QObject* parent)
: QAbstractListModel(parent), m_currentRoom(nullptr) {} : QAbstractListModel(parent), m_currentRoom(nullptr) {}
void UserListModel::setRoom(QMatrixClient::Room* room) { void UserListModel::setRoom(QMatrixClient::Room* room) {
if (m_currentRoom == room) return; if (m_currentRoom == room)
return;
using namespace QMatrixClient; using namespace QMatrixClient;
beginResetModel(); beginResetModel();
if (m_currentRoom) { if (m_currentRoom) {
m_currentRoom->disconnect(this); m_currentRoom->disconnect(this);
// m_currentRoom->connection()->disconnect(this); // m_currentRoom->connection()->disconnect(this);
for (User* user : m_users) user->disconnect(this); for (User* user : m_users)
user->disconnect(this);
m_users.clear(); m_users.clear();
} }
m_currentRoom = room; m_currentRoom = room;
@ -49,12 +51,14 @@ void UserListModel::setRoom(QMatrixClient::Room* room) {
} }
QMatrixClient::User* UserListModel::userAt(QModelIndex index) { QMatrixClient::User* UserListModel::userAt(QModelIndex index) {
if (index.row() < 0 || index.row() >= m_users.size()) return nullptr; if (index.row() < 0 || index.row() >= m_users.size())
return nullptr;
return m_users.at(index.row()); return m_users.at(index.row());
} }
QVariant UserListModel::data(const QModelIndex& index, int role) const { QVariant UserListModel::data(const QModelIndex& index, int role) const {
if (!index.isValid()) return QVariant(); if (!index.isValid())
return QVariant();
if (index.row() >= m_users.count()) { if (index.row() >= m_users.count()) {
qDebug() qDebug()
@ -71,12 +75,16 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const {
if (role == AvatarRole) { if (role == AvatarRole) {
return user->avatarMediaId(); return user->avatarMediaId();
} }
if (role == ObjectRole) {
return QVariant::fromValue(user);
}
return QVariant(); return QVariant();
} }
int UserListModel::rowCount(const QModelIndex& parent) const { int UserListModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) return 0; if (parent.isValid())
return 0;
return m_users.count(); return m_users.count();
} }
@ -111,7 +119,8 @@ void UserListModel::refresh(QMatrixClient::User* user, QVector<int> roles) {
void UserListModel::avatarChanged(QMatrixClient::User* user, void UserListModel::avatarChanged(QMatrixClient::User* user,
const QMatrixClient::Room* context) { const QMatrixClient::Room* context) {
if (context == m_currentRoom) refresh(user, {AvatarRole}); if (context == m_currentRoom)
refresh(user, {AvatarRole});
} }
int UserListModel::findUserPos(User* user) const { int UserListModel::findUserPos(User* user) const {
@ -127,5 +136,6 @@ QHash<int, QByteArray> UserListModel::roleNames() const {
roles[NameRole] = "name"; roles[NameRole] = "name";
roles[UserIDRole] = "userId"; roles[UserIDRole] = "userId";
roles[AvatarRole] = "avatar"; roles[AvatarRole] = "avatar";
roles[ObjectRole] = "user";
return roles; return roles;
} }

View File

@ -17,7 +17,12 @@ class UserListModel : public QAbstractListModel {
Q_PROPERTY( Q_PROPERTY(
QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged) QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged)
public: public:
enum EventRoles { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole }; enum EventRoles {
NameRole = Qt::UserRole + 1,
UserIDRole,
AvatarRole,
ObjectRole
};
using User = QMatrixClient::User; using User = QMatrixClient::User;