Merge branch 'multilogin' into 'master'
Multilogin See merge request b0/matrique!17
This commit is contained in:
commit
816380e9d0
|
@ -1 +1 @@
|
||||||
Subproject commit d9ff200ff62fb7f5b6b51082dc3979d5454a1bec
|
Subproject commit 875514ee865b00be67542849f94d2c2561ba4137
|
39
js/md.js
39
js/md.js
|
@ -1,44 +1,7 @@
|
||||||
/* jshint browser: true, devel: true */
|
.pragma library
|
||||||
|
|
||||||
/**
|
|
||||||
* preg_replace (from PHP) in JavaScript!
|
|
||||||
*
|
|
||||||
* This is basically a pattern replace. You can use a regex pattern to search and
|
|
||||||
* another for the replace. For more information see the PHP docs on the original
|
|
||||||
* function (http://php.net/manual/en/function.preg-replace.php), and for more on
|
|
||||||
* JavaScript flavour regex visit http://www.regular-expressions.info/javascript.html
|
|
||||||
*
|
|
||||||
* NOTE: Unlike the PHP version, this function only deals with string inputs. No arrays.
|
|
||||||
*
|
|
||||||
* @author William Duyck <fuzzyfox0@gmail.com>
|
|
||||||
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License 2.0
|
|
||||||
*
|
|
||||||
* @param {String} pattern The pattern to search for.
|
|
||||||
* @param {String} replace The string to replace.
|
|
||||||
* @param {String} subject The string to search and replace.
|
|
||||||
* @param {Integer} limit The maximum possible replacements.
|
|
||||||
* @return {String} If matches are found, the new subject will be returned.
|
|
||||||
*/
|
|
||||||
var preg_replace=function(a,b,c,d){void 0===d&&(d=-1);var e=a.substr(a.lastIndexOf(a[0])+1),f=a.substr(1,a.lastIndexOf(a[0])-1),g=RegExp(f,e),i=[],j=0,k=0,l=c,m=[];if(-1===d){do m=g.exec(c),null!==m&&i.push(m);while(null!==m&&-1!==e.indexOf("g"))}else i.push(g.exec(c));for(j=i.length-1;j>-1;j--){for(m=b,k=i[j].length;k>-1;k--)m=m.replace("${"+k+"}",i[j][k]).replace("$"+k,i[j][k]).replace("\\"+k,i[j][k]);l=l.replace(i[j][0],m)}return l};
|
var preg_replace=function(a,b,c,d){void 0===d&&(d=-1);var e=a.substr(a.lastIndexOf(a[0])+1),f=a.substr(1,a.lastIndexOf(a[0])-1),g=RegExp(f,e),i=[],j=0,k=0,l=c,m=[];if(-1===d){do m=g.exec(c),null!==m&&i.push(m);while(null!==m&&-1!==e.indexOf("g"))}else i.push(g.exec(c));for(j=i.length-1;j>-1;j--){for(m=b,k=i[j].length;k>-1;k--)m=m.replace("${"+k+"}",i[j][k]).replace("$"+k,i[j][k]).replace("\\"+k,i[j][k]);l=l.replace(i[j][0],m)}return l};
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic Markdown Parser
|
|
||||||
*
|
|
||||||
* This function parses a small subset of the Markdown language as defined by
|
|
||||||
* [John Gruber](http://daringfireball.net/projects/markdown). It's very basic
|
|
||||||
* and needs to be refactored a little, and there are plans to add more support
|
|
||||||
* for the rest of the language in the near future.
|
|
||||||
*
|
|
||||||
* This implimentation is based loosely on
|
|
||||||
* [slimdown.php](https://gist.github.com/jbroadway/2836900) by Johnny Broadway.
|
|
||||||
*
|
|
||||||
* @version 0.1
|
|
||||||
* @author William Duyck <fuzzyfox0@gmail.com>
|
|
||||||
* @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License 2.0
|
|
||||||
*
|
|
||||||
* @param {String} str A Markdown string to be converted to HTML.
|
|
||||||
* @return {String} The HTML for the given Markdown.
|
|
||||||
*/
|
|
||||||
var markdown_parser = function(str){
|
var markdown_parser = function(str){
|
||||||
|
|
||||||
var rules = [
|
var rules = [
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
.pragma library
|
||||||
|
|
||||||
|
function stringToColor(str) {
|
||||||
|
var hash = 0;
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
var colour = '#';
|
||||||
|
for (var j = 0; j < 3; j++) {
|
||||||
|
var value = (hash >> (j * 8)) & 0xFF;
|
||||||
|
colour += ('00' + value.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushToStack(stack, page) {
|
||||||
|
if(page && stack.currentItem !== page) {
|
||||||
|
if(stack.depth === 1) {
|
||||||
|
stack.replace(page)
|
||||||
|
} else {
|
||||||
|
stack.pop(null)
|
||||||
|
stack.replace(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
matrique.pro
40
matrique.pro
|
@ -7,7 +7,13 @@ CONFIG += object_parallel_to_source
|
||||||
|
|
||||||
TARGET = matrique
|
TARGET = matrique
|
||||||
|
|
||||||
|
packagesExist(QMatrixClient) {
|
||||||
|
message("Found libQMatrixClient via pkg-config.")
|
||||||
|
CONFIG += link_pkgconfig
|
||||||
|
PKGCONFIG += QMatrixClient
|
||||||
|
} else {
|
||||||
include(include/libqmatrixclient/libqmatrixclient.pri)
|
include(include/libqmatrixclient/libqmatrixclient.pri)
|
||||||
|
}
|
||||||
include(include/SortFilterProxyModel/SortFilterProxyModel.pri)
|
include(include/SortFilterProxyModel/SortFilterProxyModel.pri)
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
|
@ -29,7 +35,9 @@ SOURCES += src/main.cpp \
|
||||||
src/emojimodel.cpp \
|
src/emojimodel.cpp \
|
||||||
src/matriqueroom.cpp \
|
src/matriqueroom.cpp \
|
||||||
src/userlistmodel.cpp \
|
src/userlistmodel.cpp \
|
||||||
src/imageitem.cpp
|
src/imageitem.cpp \
|
||||||
|
src/accountlistmodel.cpp \
|
||||||
|
src/matriqueuser.cpp
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += \
|
||||||
res.qrc
|
res.qrc
|
||||||
|
@ -67,19 +75,19 @@ mac {
|
||||||
ICON = asset/img/icon.icns
|
ICON = asset/img/icon.icns
|
||||||
}
|
}
|
||||||
|
|
||||||
DISTFILES += \
|
#DISTFILES += \
|
||||||
ChatForm.qml \
|
# ChatForm.qml \
|
||||||
LoginForm.qml \
|
# LoginForm.qml \
|
||||||
main.qml \
|
# main.qml \
|
||||||
Home.qml \
|
# Home.qml \
|
||||||
Login.qml \
|
# Login.qml \
|
||||||
ImageStatus.qml \
|
# ImageStatus.qml \
|
||||||
ButtonDelegate.qml \
|
# ButtonDelegate.qml \
|
||||||
SideNav.qml \
|
# SideNav.qml \
|
||||||
RoomListForm.qml \
|
# RoomListForm.qml \
|
||||||
Room.qml \
|
# Room.qml \
|
||||||
Setting.qml \
|
# Setting.qml \
|
||||||
qml/js/md.js \
|
# qml/js/md.js \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/controller.h \
|
src/controller.h \
|
||||||
|
@ -89,4 +97,6 @@ HEADERS += \
|
||||||
src/emojimodel.h \
|
src/emojimodel.h \
|
||||||
src/matriqueroom.h \
|
src/matriqueroom.h \
|
||||||
src/userlistmodel.h \
|
src/userlistmodel.h \
|
||||||
src/imageitem.h
|
src/imageitem.h \
|
||||||
|
src/accountlistmodel.h \
|
||||||
|
src/matriqueuser.h
|
||||||
|
|
|
@ -151,12 +151,9 @@ Page {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var replaceViewFunction = function() {
|
|
||||||
if (matriqueController.isLogin) stackView.replace(roomPage)
|
|
||||||
matriqueController.isLoginChanged.disconnect(replaceViewFunction)
|
|
||||||
}
|
|
||||||
matriqueController.isLoginChanged.connect(replaceViewFunction)
|
|
||||||
controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
||||||
|
|
||||||
|
controller.connectionAdded.connect(function() { stackView.pop() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Qt.labs.settings 1.0
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
property bool lazyLoad: true
|
property bool lazyLoad: true
|
||||||
property bool richText
|
property bool richText: true
|
||||||
property bool pressAndHold
|
property bool pressAndHold
|
||||||
property bool rearrangeByActivity
|
property bool rearrangeByActivity
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
import QtQuick.Controls.Material 2.2
|
||||||
import Matrique 0.1
|
import Matrique 0.1
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
|
||||||
|
|
205
qml/Setting.qml
205
qml/Setting.qml
|
@ -2,56 +2,184 @@ import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
import Matrique 0.1
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
|
||||||
import "component"
|
import "component"
|
||||||
import "form"
|
import "form"
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
property var connection
|
property alias listModel: accountSettingsListView.model
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: accountForm
|
id: accountForm
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
padding: 64
|
padding: 64
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
RowLayout {
|
anchors.fill: parent
|
||||||
Layout.preferredHeight: 60
|
|
||||||
|
|
||||||
ImageStatus {
|
ListView {
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
source: matriqueController.isLogin ? connection.localUser && connection.localUser.avatarUrl ? "image://mxc/" + connection.localUser.avatarUrl : "" : "qrc:/asset/img/avatar.png"
|
|
||||||
displayText: matriqueController.isLogin && connection.localUser.displayName ? connection.localUser.displayName : ""
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
Label {
|
id: accountSettingsListView
|
||||||
font.pointSize: 18
|
|
||||||
text: matriqueController.isLogin ? connection.localUser.displayName : ""
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
delegate: Column {
|
||||||
|
property bool expanded: false
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
SwipeDelegate {
|
||||||
|
width: accountSettingsListView.width
|
||||||
|
height: 64
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
ImageItem {
|
||||||
|
width: parent.height
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
hint: user.displayName
|
||||||
|
defaultColor: Util.stringToColor(user.displayName)
|
||||||
|
image: user.avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
Label {
|
Label {
|
||||||
font.pointSize: 12
|
text: user.displayName
|
||||||
text: matriqueController.isLogin ? connection.localUser.id : ""
|
}
|
||||||
|
Label {
|
||||||
|
text: user.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swipe.right: Rectangle {
|
||||||
|
width: parent.height
|
||||||
|
height: parent.height
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
color: Material.accent
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
icon: "\ue879"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
SwipeDelegate.onClicked: matriqueController.logout(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
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: matriqueController.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
text: "Logout"
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Add Account"
|
||||||
|
flat: true
|
||||||
highlighted: true
|
highlighted: true
|
||||||
|
|
||||||
onClicked: {
|
onClicked: stackView.push(loginPage)
|
||||||
matriqueController.logout()
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,6 +189,8 @@ Page {
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
|
padding: 64
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Switch {
|
Switch {
|
||||||
text: "Lazy load at initial sync"
|
text: "Lazy load at initial sync"
|
||||||
|
@ -90,6 +220,8 @@ Page {
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
|
padding: 64
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Switch {
|
Switch {
|
||||||
text: "Dark theme"
|
text: "Dark theme"
|
||||||
|
@ -116,6 +248,7 @@ Page {
|
||||||
|
|
||||||
Page {
|
Page {
|
||||||
id: aboutForm
|
id: aboutForm
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
padding: 64
|
padding: 64
|
||||||
|
@ -133,49 +266,53 @@ Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
Rectangle {
|
||||||
ColumnLayout {
|
width: 240
|
||||||
Layout.preferredWidth: 240
|
height: parent.height
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
spacing: 0
|
id: settingDrawer
|
||||||
|
|
||||||
|
color: MSettings.darkTheme ? "#323232" : "#f3f3f3"
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
|
|
||||||
text: "Account"
|
text: "Account"
|
||||||
onClicked: pushToStack(accountForm)
|
onClicked: pushToStack(accountForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
|
|
||||||
text: "General"
|
text: "General"
|
||||||
onClicked: pushToStack(generalForm)
|
onClicked: pushToStack(generalForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
|
|
||||||
text: "Appearance"
|
text: "Appearance"
|
||||||
onClicked: pushToStack(appearanceForm)
|
onClicked: pushToStack(appearanceForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
Layout.fillWidth: true
|
width: parent.width
|
||||||
|
|
||||||
text: "About"
|
text: "About"
|
||||||
onClicked: pushToStack(aboutForm)
|
onClicked: pushToStack(aboutForm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
StackView {
|
StackView {
|
||||||
Layout.fillWidth: true
|
anchors.fill: parent
|
||||||
Layout.fillHeight: true
|
anchors.leftMargin: settingDrawer.width
|
||||||
|
|
||||||
id: settingStackView
|
id: settingStackView
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function pushToStack(item) {
|
function pushToStack(item) {
|
||||||
settingStackView.clear()
|
settingStackView.clear()
|
||||||
|
|
|
@ -15,7 +15,11 @@ Control {
|
||||||
AutoMouseArea {
|
AutoMouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/MessageContextMenu.qml").createObject(this)
|
onSecondaryClicked: {
|
||||||
|
messageContextMenu.row = messageRow
|
||||||
|
messageContextMenu.model = model
|
||||||
|
messageContextMenu.popup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle { color: colored ? Material.accent : highlighted ? Material.primary : backgroundColor }
|
background: Rectangle { color: colored ? Material.accent : highlighted ? Material.primary : backgroundColor }
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
Item {
|
|
||||||
property bool round: true
|
|
||||||
property string source: ""
|
|
||||||
property string displayText: ""
|
|
||||||
readonly property bool showImage: source
|
|
||||||
readonly property bool showInitial: !showImage && displayText || avatar.status != Image.Ready
|
|
||||||
|
|
||||||
id: item
|
|
||||||
|
|
||||||
Image {
|
|
||||||
width: item.width
|
|
||||||
height: item.width
|
|
||||||
|
|
||||||
id: avatar
|
|
||||||
|
|
||||||
visible: showImage
|
|
||||||
source: item.source
|
|
||||||
|
|
||||||
mipmap: true
|
|
||||||
layer.enabled: true
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
sourceSize.width: item.width
|
|
||||||
|
|
||||||
layer.effect: OpacityMask {
|
|
||||||
maskSource: Item {
|
|
||||||
width: avatar.width
|
|
||||||
height: avatar.width
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: avatar.width
|
|
||||||
height: avatar.width
|
|
||||||
radius: round? avatar.width / 2 : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
color: "white"
|
|
||||||
visible: showInitial
|
|
||||||
text: showInitial ? getInitials(displayText)[0] : ""
|
|
||||||
font.pixelSize: 22
|
|
||||||
font.bold: true
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
background: Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
radius: round? width / 2 : 0
|
|
||||||
color: showInitial ? stringToColor(displayText) : Material.accent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInitials(text) {
|
|
||||||
if (!text) return "N"
|
|
||||||
var initial = text.toUpperCase().replace(/[^a-zA-Z- ]/g, "").match(/\b\w/g);
|
|
||||||
if (!initial) return "N"
|
|
||||||
return initial
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringToColor(str) {
|
|
||||||
var hash = 0;
|
|
||||||
for (var i = 0; i < str.length; i++) {
|
|
||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
||||||
}
|
|
||||||
var colour = '#';
|
|
||||||
for (var j = 0; j < 3; j++) {
|
|
||||||
var value = (hash >> (j * 8)) & 0xFF;
|
|
||||||
colour += ('00' + value.toString(16)).substr(-2);
|
|
||||||
}
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,20 +3,13 @@ import QtQuick.Controls 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
|
||||||
Item {
|
|
||||||
property alias icon: iconText.text
|
|
||||||
property var color: MSettings.darkTheme ? "white" : "black"
|
|
||||||
|
|
||||||
id: item
|
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.fill: parent
|
property alias icon: materialLabel.text
|
||||||
|
|
||||||
|
id: materialLabel
|
||||||
|
|
||||||
id: iconText
|
|
||||||
font.pointSize: 16
|
font.pointSize: 16
|
||||||
font.family: materialFont.name
|
font.family: materialFont.name
|
||||||
color: item.color
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -4,9 +4,10 @@ import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.2
|
||||||
import Matrique 0.1
|
import Matrique 0.1
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
readonly property bool avatarVisible: !(sentByMe || (aboveAuthor === author && section === aboveSection))
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote")
|
||||||
readonly property bool highlighted: !(sentByMe || eventType === "notice" )
|
readonly property bool highlighted: !(sentByMe || eventType === "notice" )
|
||||||
readonly property bool sentByMe: author === currentRoom.localUser
|
readonly property bool sentByMe: author === currentRoom.localUser
|
||||||
readonly property bool isText: eventType === "notice" || eventType === "message"
|
readonly property bool isText: eventType === "notice" || eventType === "message"
|
||||||
|
@ -22,15 +23,16 @@ RowLayout {
|
||||||
|
|
||||||
spacing: 6
|
spacing: 6
|
||||||
|
|
||||||
ImageStatus {
|
ImageItem {
|
||||||
Layout.preferredWidth: 40
|
Layout.preferredWidth: 40
|
||||||
Layout.preferredHeight: 40
|
Layout.preferredHeight: 40
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
round: false
|
round: false
|
||||||
visible: avatarVisible
|
visible: avatarVisible
|
||||||
source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : null
|
hint: author.displayName
|
||||||
displayText: author.displayName
|
defaultColor: Util.stringToColor(author.displayName)
|
||||||
|
image: author.avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -61,6 +63,11 @@ RowLayout {
|
||||||
Material.foreground: Material.accent
|
Material.foreground: Material.accent
|
||||||
coloredBackground: highlighted
|
coloredBackground: highlighted
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: inputField.insert(inputField.cursorPosition, author.displayName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoLabel {
|
AutoLabel {
|
||||||
|
@ -86,14 +93,30 @@ RowLayout {
|
||||||
active: eventType === "image" || eventType === "file" || eventType === "audio"
|
active: eventType === "image" || eventType === "file" || eventType === "audio"
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoLabel {
|
Row {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
AutoLabel {
|
||||||
|
id: timeLabel
|
||||||
|
|
||||||
visible: Math.abs(time - aboveTime) > 600000 || index == 0
|
visible: Math.abs(time - aboveTime) > 600000 || index == 0
|
||||||
text: Qt.formatTime(time, "hh:mm")
|
text: Qt.formatTime(time, "hh:mm")
|
||||||
coloredBackground: highlighted
|
coloredBackground: highlighted
|
||||||
Material.foreground: "grey"
|
Material.foreground: "grey"
|
||||||
font.pointSize: 8
|
font.pointSize: 8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
height: timeLabel.height
|
||||||
|
|
||||||
|
visible: userMarker.length > 0
|
||||||
|
icon: "\ue5ca"
|
||||||
|
color: highlighted ? "white": Material.foreground
|
||||||
|
font.pointSize: 12
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import QtQuick.Controls.Material 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import Matrique 0.1
|
import Matrique 0.1
|
||||||
|
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
Drawer {
|
Drawer {
|
||||||
property var room
|
property var room
|
||||||
|
|
||||||
|
@ -22,13 +24,14 @@ Drawer {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 32
|
anchors.margins: 32
|
||||||
|
|
||||||
ImageStatus {
|
ImageItem {
|
||||||
Layout.preferredWidth: 64
|
Layout.preferredWidth: 64
|
||||||
Layout.preferredHeight: 64
|
Layout.preferredHeight: 64
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
source: room && room.avatarUrl != "" ? "image://mxc/" + room.avatarUrl : null
|
hint: room ? room.displayName : "No name"
|
||||||
displayText: room ? room.displayName : ""
|
defaultColor: Util.stringToColor(room ? room.displayName : "No name")
|
||||||
|
image: matriqueController.safeImage(room ? room.avatar : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -45,6 +48,13 @@ Drawer {
|
||||||
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
|
text: room && room.canonicalAlias ? room.canonicalAlias : "No Canonical Alias"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: room ? room.memberCount + " Members" : "No Member Count"
|
||||||
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
@ -53,6 +63,7 @@ Drawer {
|
||||||
|
|
||||||
id: roomNameField
|
id: roomNameField
|
||||||
text: room && room.name ? room.name : ""
|
text: room && room.name ? room.name : ""
|
||||||
|
selectByMouse: true
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
|
@ -74,6 +85,7 @@ Drawer {
|
||||||
id: roomTopicField
|
id: roomTopicField
|
||||||
|
|
||||||
text: room && room.topic ? room.topic : ""
|
text: room && room.topic ? room.topic : ""
|
||||||
|
selectByMouse: true
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
|
@ -94,7 +106,11 @@ Drawer {
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
model: UserListModel {
|
||||||
|
room: roomDrawer.room
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: SwipeDelegate {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 48
|
height: 48
|
||||||
|
|
||||||
|
@ -103,12 +119,13 @@ Drawer {
|
||||||
anchors.margins: 8
|
anchors.margins: 8
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
ImageStatus {
|
ImageItem {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
source: avatar != "" ? "image://mxc/" + avatar : ""
|
defaultColor: Util.stringToColor(name)
|
||||||
displayText: name
|
image: avatar
|
||||||
|
hint: name
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -117,12 +134,25 @@ Drawer {
|
||||||
text: name
|
text: name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swipe.right: Rectangle {
|
||||||
|
width: parent.height
|
||||||
|
height: parent.height
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
color: Material.accent
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
icon: "\ue8fb"
|
||||||
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
model: UserListModel {
|
SwipeDelegate.onClicked: room.kickMember(userId)
|
||||||
id: userListModel
|
}
|
||||||
|
|
||||||
room: roomDrawer.room
|
onClicked: inputField.insert(inputField.cursorPosition, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
|
@ -3,29 +3,23 @@ import QtQuick.Controls 2.2
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.2
|
||||||
|
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
ItemDelegate {
|
ItemDelegate {
|
||||||
property var page
|
property var page
|
||||||
readonly property bool selected: stackView.currentItem === page
|
property bool selected: stackView.currentItem === page
|
||||||
|
property color highlightColor: Material.accent
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: selected ? 4 : 0
|
width: selected ? 4 : 0
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
color: Material.accent
|
color: highlightColor
|
||||||
|
|
||||||
Behavior on width {
|
Behavior on width {
|
||||||
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: Util.pushToStack(stackView, page)
|
||||||
if(page && stackView.currentItem !== page) {
|
|
||||||
if(stackView.depth === 1) {
|
|
||||||
stackView.replace(page)
|
|
||||||
} else {
|
|
||||||
stackView.clear()
|
|
||||||
stackView.push(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
import QtQuick.Dialogs 1.2
|
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.2
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import Matrique 0.1
|
import Matrique 0.1
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
|
||||||
import "../component"
|
import "qrc:/qml/component"
|
||||||
|
import "qrc:/qml/menu"
|
||||||
import "qrc:/js/md.js" as Markdown
|
import "qrc:/js/md.js" as Markdown
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property var currentRoom: null
|
property var currentRoom: null
|
||||||
|
@ -57,12 +58,13 @@ Item {
|
||||||
|
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
ImageStatus {
|
ImageItem {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
source: currentRoom && currentRoom.avatarUrl != "" ? "image://mxc/" + currentRoom.avatarUrl : null
|
hint: currentRoom ? currentRoom.displayName : "No name"
|
||||||
displayText: currentRoom ? currentRoom.displayName : ""
|
defaultColor: Util.stringToColor(currentRoom ? currentRoom.displayName : "No name")
|
||||||
|
image: matriqueController.safeImage(currentRoom ? currentRoom.avatar : null)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
@ -87,7 +89,7 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
text: currentRoom ? currentRoom.topic : ""
|
text: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
||||||
color: "white"
|
color: "white"
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
|
@ -223,6 +225,36 @@ Item {
|
||||||
|
|
||||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MessageContextMenu { id: messageContextMenu }
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
property string sourceText
|
||||||
|
|
||||||
|
x: (window.width - width) / 2
|
||||||
|
y: (window.height - height) / 2
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: sourceDialog
|
||||||
|
|
||||||
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
standardButtons: Dialog.Ok
|
||||||
|
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
title: "View Source"
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
TextArea {
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
|
||||||
|
text: sourceDialog.sourceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScrollBar {
|
ScrollBar {
|
||||||
|
|
|
@ -8,7 +8,9 @@ import Matrique 0.1
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
import Matrique.Settings 0.1
|
import Matrique.Settings 0.1
|
||||||
|
|
||||||
import "../component"
|
import "qrc:/qml/component"
|
||||||
|
import "qrc:/qml/menu"
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property alias listModel: roomListProxyModel.sourceModel
|
property alias listModel: roomListProxyModel.sourceModel
|
||||||
|
@ -38,7 +40,7 @@ Item {
|
||||||
bottomPadding: 0
|
bottomPadding: 0
|
||||||
placeholderText: "Search..."
|
placeholderText: "Search..."
|
||||||
|
|
||||||
background: Rectangle { color: MSettings.darkTheme ? "#282828" : "#fafafa" }
|
background: Rectangle { color: MSettings.darkTheme ? "#303030" : "#fafafa" }
|
||||||
|
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: StandardKey.Find
|
sequence: StandardKey.Find
|
||||||
|
@ -106,15 +108,25 @@ Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 64
|
height: 64
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#282828" : "#fafafa"
|
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
||||||
|
|
||||||
AutoMouseArea {
|
AutoMouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
hoverEnabled: MSettings.miniMode
|
hoverEnabled: MSettings.miniMode
|
||||||
|
|
||||||
onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/RoomContextMenu.qml").createObject(this)
|
onSecondaryClicked: {
|
||||||
onPrimaryClicked: category === RoomType.Invited ? inviteDialog.open() : enteredRoom = currentRoom
|
roomContextMenu.model = model
|
||||||
|
roomContextMenu.popup()
|
||||||
|
}
|
||||||
|
onPrimaryClicked: {
|
||||||
|
if (category === RoomType.Invited) {
|
||||||
|
inviteDialog.currentRoom = currentRoom
|
||||||
|
inviteDialog.open()
|
||||||
|
} else {
|
||||||
|
enteredRoom = currentRoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ToolTip.visible: MSettings.miniMode && containsMouse
|
ToolTip.visible: MSettings.miniMode && containsMouse
|
||||||
ToolTip.text: name
|
ToolTip.text: name
|
||||||
|
@ -129,11 +141,14 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: 4
|
width: unreadCount > 0 || highlighted ? 4 : 0
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
color: Material.accent
|
color: Material.accent
|
||||||
visible: unreadCount > 0 || highlighted
|
|
||||||
|
Behavior on width {
|
||||||
|
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -142,14 +157,6 @@ Item {
|
||||||
|
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
// ImageStatus {
|
|
||||||
// Layout.preferredWidth: height
|
|
||||||
// Layout.fillHeight: true
|
|
||||||
|
|
||||||
// source: avatar ? "image://mxc/" + avatar : ""
|
|
||||||
// displayText: name
|
|
||||||
// }
|
|
||||||
|
|
||||||
ImageItem {
|
ImageItem {
|
||||||
id: imageItem
|
id: imageItem
|
||||||
|
|
||||||
|
@ -157,7 +164,7 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
hint: name || "No Name"
|
hint: name || "No Name"
|
||||||
defaultColor: stringToColor(name || "No Name")
|
defaultColor: Util.stringToColor(name || "No Name")
|
||||||
|
|
||||||
image: avatar
|
image: avatar
|
||||||
}
|
}
|
||||||
|
@ -183,7 +190,7 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,"");
|
text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,"")
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
wrapMode: Text.NoWrap
|
wrapMode: Text.NoWrap
|
||||||
}
|
}
|
||||||
|
@ -205,7 +212,11 @@ Item {
|
||||||
horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined
|
horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RoomContextMenu { id: roomContextMenu }
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
|
property var currentRoom
|
||||||
|
|
||||||
id: inviteDialog
|
id: inviteDialog
|
||||||
parent: ApplicationWindow.overlay
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
@ -224,17 +235,4 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringToColor(str) {
|
|
||||||
var hash = 0;
|
|
||||||
for (var i = 0; i < str.length; i++) {
|
|
||||||
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
||||||
}
|
|
||||||
var colour = '#';
|
|
||||||
for (var j = 0; j < 3; j++) {
|
|
||||||
var value = (hash >> (j * 8)) & 0xFF;
|
|
||||||
colour += ('00' + value.toString(16)).substr(-2);
|
|
||||||
}
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
80
qml/main.qml
80
qml/main.qml
|
@ -9,9 +9,10 @@ import Matrique.Settings 0.1
|
||||||
|
|
||||||
import "component"
|
import "component"
|
||||||
import "form"
|
import "form"
|
||||||
|
import "qrc:/js/util.js" as Util
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
readonly property var connection: matriqueController.connection
|
readonly property var currentConnection: accountListView.currentConnection ? accountListView.currentConnection : null
|
||||||
|
|
||||||
width: 960
|
width: 960
|
||||||
height: 640
|
height: 640
|
||||||
|
@ -25,11 +26,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
||||||
|
|
||||||
Settings {
|
Material.accent: matriqueController.color(currentConnection ? currentConnection.localUserId : "")
|
||||||
property alias homeserver: matriqueController.homeserver
|
|
||||||
property alias userID: matriqueController.userID
|
|
||||||
property alias token: matriqueController.token
|
|
||||||
}
|
|
||||||
|
|
||||||
FontLoader { id: materialFont; source: "qrc:/asset/font/material.ttf" }
|
FontLoader { id: materialFont; source: "qrc:/asset/font/material.ttf" }
|
||||||
|
|
||||||
|
@ -48,6 +45,11 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AccountListModel {
|
||||||
|
id: accountListModel
|
||||||
|
controller: matriqueController
|
||||||
|
}
|
||||||
|
|
||||||
Popup {
|
Popup {
|
||||||
property bool busy: matriqueController.busy
|
property bool busy: matriqueController.busy
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
connection: window.connection
|
connection: currentConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
Setting {
|
Setting {
|
||||||
|
@ -85,7 +87,7 @@ ApplicationWindow {
|
||||||
|
|
||||||
parent: null
|
parent: null
|
||||||
|
|
||||||
connection: window.connection
|
listModel: accountListModel
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -104,25 +106,41 @@ ApplicationWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
SideNavButton {
|
ListView {
|
||||||
Layout.fillWidth: true
|
property var currentConnection: null
|
||||||
Layout.preferredHeight: width
|
|
||||||
|
|
||||||
ImageStatus {
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
id: accountListView
|
||||||
|
|
||||||
|
model: accountListModel
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
delegate: SideNavButton {
|
||||||
|
width: parent.width
|
||||||
|
height: width
|
||||||
|
|
||||||
|
selected: stackView.currentItem === page && currentConnection === connection
|
||||||
|
|
||||||
|
ImageItem {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 12
|
anchors.margins: 12
|
||||||
|
|
||||||
source: matriqueController.isLogin ? connection.localUser && connection.localUser.avatarUrl ? "image://mxc/" + connection.localUser.avatarUrl : "" : "qrc:/asset/img/avatar.png"
|
hint: user.displayName
|
||||||
displayText: matriqueController.isLogin && connection.localUser.displayName ? connection.localUser.displayName : ""
|
image: user.avatar
|
||||||
|
defaultColor: Util.stringToColor(user.displayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
highlightColor: matriqueController.color(user.id)
|
||||||
|
|
||||||
page: roomPage
|
page: roomPage
|
||||||
|
|
||||||
|
onClicked: accountListView.currentConnection = connection
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
color: "transparent"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SideNavButton {
|
SideNavButton {
|
||||||
|
@ -174,7 +192,7 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: matriqueController.createRoom(addRoomDialogNameTextField.text, addRoomDialogTopicTextField.text)
|
onAccepted: matriqueController.createRoom(currentConnection, addRoomDialogNameTextField.text, addRoomDialogTopicTextField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
|
@ -200,7 +218,7 @@ ApplicationWindow {
|
||||||
placeholderText: "#matrix:matrix.org"
|
placeholderText: "#matrix:matrix.org"
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: matriqueController.joinRoom(joinRoomDialogTextField.text)
|
onAccepted: matriqueController.joinRoom(currentConnection, joinRoomDialogTextField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +245,7 @@ ApplicationWindow {
|
||||||
placeholderText: "@bot:matrix.org"
|
placeholderText: "@bot:matrix.org"
|
||||||
}
|
}
|
||||||
|
|
||||||
onAccepted: matriqueController.createDirectChat(directChatDialogTextField.text)
|
onAccepted: currentConnection.createDirectChat(directChatDialogTextField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -241,7 +259,7 @@ ApplicationWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
icon: "\ue8b8"
|
icon: "\ue8b8"
|
||||||
color: parent.highlighted ? Material.accent : "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
page: settingPage
|
page: settingPage
|
||||||
}
|
}
|
||||||
|
@ -272,13 +290,15 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Binding {
|
||||||
imageProvider.connection = matriqueController.connection
|
target: imageProvider
|
||||||
|
property: "connection"
|
||||||
|
value: currentConnection
|
||||||
|
}
|
||||||
|
|
||||||
if (matriqueController.userID && matriqueController.token) {
|
Component.onCompleted: {
|
||||||
matriqueController.login();
|
matriqueController.initiated.connect(function() {
|
||||||
} else {
|
if (matriqueController.accountCount == 0) stackView.push(loginPage)
|
||||||
stackView.replace(loginPage);
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,42 +2,45 @@ import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
readonly property bool isFile: eventType === "video" || eventType === "audio" || eventType === "file" || eventType === "image"
|
property var row: null
|
||||||
|
property var model: null
|
||||||
|
|
||||||
|
readonly property bool isFile: model && (model.eventType === "video" || model.eventType === "audio" || model.eventType === "file" || model.eventType === "image")
|
||||||
|
|
||||||
id: messageContextMenu
|
id: messageContextMenu
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Copy"
|
text: "Copy"
|
||||||
|
|
||||||
onTriggered: matriqueController.copyToClipboard(plainText)
|
onTriggered: matriqueController.copyToClipboard(model.plainText)
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Copy Source"
|
text: "View Source"
|
||||||
|
|
||||||
onTriggered: matriqueController.copyToClipboard(toolTip)
|
onTriggered: {
|
||||||
|
sourceDialog.sourceText = model.toolTip
|
||||||
|
sourceDialog.open()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
visible: isFile
|
visible: isFile
|
||||||
height: visible ? undefined : 0
|
height: visible ? undefined : 0
|
||||||
text: "Open Externally"
|
text: "Open Externally"
|
||||||
|
|
||||||
onTriggered: messageRow.openExternally()
|
onTriggered: row.openExternally()
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
visible: isFile
|
visible: isFile
|
||||||
height: visible ? undefined : 0
|
height: visible ? undefined : 0
|
||||||
text: "Save As"
|
text: "Save As"
|
||||||
|
|
||||||
onTriggered: messageRow.saveFileAs()
|
onTriggered: row.saveFileAs()
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
visible: sentByMe
|
visible: model && model.author === currentRoom.localUser
|
||||||
height: visible ? undefined : 0
|
height: visible ? undefined : 0
|
||||||
text: "Redact"
|
text: "Redact"
|
||||||
|
|
||||||
onTriggered: currentRoom.redactEvent(eventId)
|
onTriggered: currentRoom.redactEvent(model.eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: popup()
|
|
||||||
onClosed: messageContextMenu.destroy()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
import Matrique 0.1
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
|
property var model: null
|
||||||
|
|
||||||
id: roomListMenu
|
id: roomListMenu
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Favourite"
|
text: "Favourite"
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: currentRoom && currentRoom.isFavourite
|
checked: model && model.category === RoomType.Favorite
|
||||||
|
|
||||||
onTriggered: currentRoom.isFavourite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", "1")
|
onTriggered: model.category === RoomType.Favorite ? model.currentRoom.removeTag("m.favourite") : model.currentRoom.addTag("m.favourite", "1")
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Deprioritize"
|
text: "Deprioritize"
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: currentRoom && currentRoom.isLowPriority
|
checked: model && model.category === RoomType.Deprioritized
|
||||||
|
|
||||||
onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1")
|
onTriggered: model.category === RoomType.Deprioritized ? model.currentRoom.removeTag("m.lowpriority") : model.currentRoom.addTag("m.lowpriority", "1")
|
||||||
}
|
}
|
||||||
MenuSeparator {}
|
MenuSeparator {}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Mark as Read"
|
text: "Mark as Read"
|
||||||
|
|
||||||
onTriggered: currentRoom.markAllMessagesAsRead()
|
onTriggered: model.currentRoom.markAllMessagesAsRead()
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Leave Room"
|
text: "Leave Room"
|
||||||
|
|
||||||
onTriggered: currentRoom.forget()
|
onTriggered: model.currentRoom.forget()
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: popup()
|
|
||||||
onClosed: roomListMenu.destroy()
|
|
||||||
}
|
}
|
||||||
|
|
2
res.qrc
2
res.qrc
|
@ -6,7 +6,6 @@
|
||||||
<file>asset/font/material.ttf</file>
|
<file>asset/font/material.ttf</file>
|
||||||
<file>qml/Login.qml</file>
|
<file>qml/Login.qml</file>
|
||||||
<file>qml/main.qml</file>
|
<file>qml/main.qml</file>
|
||||||
<file>qml/component/ImageStatus.qml</file>
|
|
||||||
<file>qml/form/RoomForm.qml</file>
|
<file>qml/form/RoomForm.qml</file>
|
||||||
<file>qml/Room.qml</file>
|
<file>qml/Room.qml</file>
|
||||||
<file>qml/component/SideNavButton.qml</file>
|
<file>qml/component/SideNavButton.qml</file>
|
||||||
|
@ -30,5 +29,6 @@
|
||||||
<file>qml/component/StateDelegate.qml</file>
|
<file>qml/component/StateDelegate.qml</file>
|
||||||
<file>qml/component/AutoLabel.qml</file>
|
<file>qml/component/AutoLabel.qml</file>
|
||||||
<file>qml/component/RoomDrawer.qml</file>
|
<file>qml/component/RoomDrawer.qml</file>
|
||||||
|
<file>js/util.js</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
#include "accountlistmodel.h"
|
||||||
|
|
||||||
|
#include "room.h"
|
||||||
|
|
||||||
|
AccountListModel::AccountListModel(QObject* parent)
|
||||||
|
: QAbstractListModel(parent) {}
|
||||||
|
|
||||||
|
void AccountListModel::setController(Controller* value) {
|
||||||
|
if (m_controller != value) {
|
||||||
|
beginResetModel();
|
||||||
|
m_connections.clear();
|
||||||
|
|
||||||
|
m_controller = value;
|
||||||
|
|
||||||
|
for (auto c : m_controller->connections()) m_connections.append(c);
|
||||||
|
|
||||||
|
connect(m_controller, &Controller::connectionAdded, this,
|
||||||
|
[=](Connection* conn) {
|
||||||
|
if (!conn) {
|
||||||
|
}
|
||||||
|
beginInsertRows(QModelIndex(), m_connections.count(),
|
||||||
|
m_connections.count());
|
||||||
|
m_connections.append(conn);
|
||||||
|
endInsertRows();
|
||||||
|
});
|
||||||
|
connect(m_controller, &Controller::connectionDropped, this,
|
||||||
|
[=](Connection* conn) {
|
||||||
|
qDebug() << "Dropping connection" << conn->userId();
|
||||||
|
if (!conn) {
|
||||||
|
qDebug() << "Trying to remove null connection";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
conn->disconnect(this);
|
||||||
|
const auto it =
|
||||||
|
std::find(m_connections.begin(), m_connections.end(), conn);
|
||||||
|
if (it == m_connections.end())
|
||||||
|
return; // Already deleted, nothing to do
|
||||||
|
const int row = it - m_connections.begin();
|
||||||
|
beginRemoveRows(QModelIndex(), row, row);
|
||||||
|
m_connections.erase(it);
|
||||||
|
endRemoveRows();
|
||||||
|
});
|
||||||
|
emit controllerChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant AccountListModel::data(const QModelIndex& index, int role) const {
|
||||||
|
if (!index.isValid()) return QVariant();
|
||||||
|
|
||||||
|
if (index.row() >= m_connections.count()) {
|
||||||
|
qDebug() << "AccountListModel, something's wrong: index.row() >= "
|
||||||
|
"m_users.count()";
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
auto m_connection = m_connections.at(index.row());
|
||||||
|
if (role == UserRole) {
|
||||||
|
return QVariant::fromValue(m_connection->user());
|
||||||
|
}
|
||||||
|
if (role == ConnectionRole) {
|
||||||
|
return QVariant::fromValue(m_connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
int AccountListModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
if (parent.isValid()) return 0;
|
||||||
|
|
||||||
|
return m_connections.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> AccountListModel::roleNames() const {
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
roles[UserRole] = "user";
|
||||||
|
roles[ConnectionRole] = "connection";
|
||||||
|
return roles;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef ACCOUNTLISTMODEL_H
|
||||||
|
#define ACCOUNTLISTMODEL_H
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class AccountListModel : public QAbstractListModel {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY
|
||||||
|
controllerChanged)
|
||||||
|
public:
|
||||||
|
enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole };
|
||||||
|
|
||||||
|
AccountListModel(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
QVariant data(const QModelIndex& index, int role = UserRole) const override;
|
||||||
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Controller* controller() { return m_controller; }
|
||||||
|
void setController(Controller* value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Controller* m_controller;
|
||||||
|
QVector<Connection*> m_connections;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void controllerChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ACCOUNTLISTMODEL_H
|
|
@ -1,6 +1,8 @@
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
#include "matriqueroom.h"
|
#include "matriqueroom.h"
|
||||||
|
#include "matriqueuser.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
#include "events/eventcontent.h"
|
#include "events/eventcontent.h"
|
||||||
#include "events/roommessageevent.h"
|
#include "events/roommessageevent.h"
|
||||||
|
@ -8,7 +10,21 @@
|
||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QSystemTrayIcon>
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QElapsedTimer>
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
#include <QtCore/QStandardPaths>
|
||||||
|
#include <QtCore/QStringBuilder>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtGui/QCloseEvent>
|
||||||
|
#include <QtGui/QDesktopServices>
|
||||||
|
#include <QtGui/QMovie>
|
||||||
|
#include <QtGui/QPixmap>
|
||||||
|
#include <QtNetwork/QAuthenticator>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
|
||||||
Controller::Controller(QObject* parent) : QObject(parent) {
|
Controller::Controller(QObject* parent) : QObject(parent) {
|
||||||
tray->setIcon(QIcon(":/asset/img/icon.png"));
|
tray->setIcon(QIcon(":/asset/img/icon.png"));
|
||||||
|
@ -24,87 +40,169 @@ Controller::Controller(QObject* parent) : QObject(parent) {
|
||||||
tray->show();
|
tray->show();
|
||||||
|
|
||||||
Connection::setRoomType<MatriqueRoom>();
|
Connection::setRoomType<MatriqueRoom>();
|
||||||
|
Connection::setUserType<MatriqueUser>();
|
||||||
|
|
||||||
connect(m_connection, &Connection::connected, this, &Controller::connected);
|
QTimer::singleShot(0, this, SLOT(invokeLogin()));
|
||||||
connect(m_connection, &Connection::resolveError, this,
|
|
||||||
&Controller::reconnect);
|
|
||||||
connect(m_connection, &Connection::syncError, this, &Controller::reconnect);
|
|
||||||
connect(m_connection, &Connection::syncDone, this, &Controller::resync);
|
|
||||||
connect(m_connection, &Connection::connected, this,
|
|
||||||
&Controller::connectionChanged);
|
|
||||||
|
|
||||||
connect(m_connection, &Connection::connected, [=] { setBusy(true); });
|
|
||||||
connect(m_connection, &Connection::syncDone, [=] { setBusy(false); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller::~Controller() {
|
Controller::~Controller() {}
|
||||||
m_connection->saveState();
|
|
||||||
m_connection->stopSync();
|
|
||||||
m_connection->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::login() {
|
inline QString accessTokenFileName(const AccountSettings& account) {
|
||||||
if (!m_isLogin) {
|
QString fileName = account.userId();
|
||||||
m_connection->setHomeserver(QUrl(m_homeserver));
|
fileName.replace(':', '_');
|
||||||
m_connection->connectWithToken(m_userID, m_token, "");
|
return QStandardPaths::writableLocation(
|
||||||
}
|
QStandardPaths::AppLocalDataLocation) +
|
||||||
|
'/' + fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
||||||
QString pass) {
|
QString pass) {
|
||||||
if (!m_isLogin) {
|
|
||||||
if (!user.isEmpty() && !pass.isEmpty()) {
|
if (!user.isEmpty() && !pass.isEmpty()) {
|
||||||
|
Connection* m_connection = new Connection(this);
|
||||||
m_connection->setHomeserver(QUrl(serverAddr));
|
m_connection->setHomeserver(QUrl(serverAddr));
|
||||||
m_connection->connectToServer(user, pass, "");
|
m_connection->connectToServer(user, pass, "");
|
||||||
|
connect(m_connection, &Connection::connected, [=] {
|
||||||
|
AccountSettings account(m_connection->userId());
|
||||||
|
account.setKeepLoggedIn(true);
|
||||||
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
|
account.setHomeserver(m_connection->homeserver());
|
||||||
|
account.setDeviceId(m_connection->deviceId());
|
||||||
|
account.setDeviceName("Matrique");
|
||||||
|
if (!saveAccessToken(account, m_connection->accessToken()))
|
||||||
|
qWarning() << "Couldn't save access token";
|
||||||
|
account.sync();
|
||||||
|
addConnection(m_connection);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::logout(Connection* conn) {
|
||||||
|
if (!conn) {
|
||||||
|
qCritical() << "Attempt to logout null connection";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsGroup("Accounts").remove(conn->userId());
|
||||||
|
QFile(accessTokenFileName(AccountSettings(conn->userId()))).remove();
|
||||||
|
|
||||||
|
conn->logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::addConnection(Connection* c) {
|
||||||
|
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
||||||
|
|
||||||
|
m_connections.push_back(c);
|
||||||
|
|
||||||
|
connect(c, &Connection::syncDone, this, [=] {
|
||||||
|
c->saveState();
|
||||||
|
c->sync(30000);
|
||||||
|
});
|
||||||
|
connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); });
|
||||||
|
|
||||||
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
|
c->sync(30000);
|
||||||
|
|
||||||
|
emit connectionAdded(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::dropConnection(Connection* c) {
|
||||||
|
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
|
||||||
|
m_connections.removeOne(c);
|
||||||
|
|
||||||
|
emit connectionDropped(c);
|
||||||
|
c->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::invokeLogin() {
|
||||||
|
using namespace QMatrixClient;
|
||||||
|
const auto accounts = SettingsGroup("Accounts").childGroups();
|
||||||
|
for (const auto& accountId : accounts) {
|
||||||
|
AccountSettings account{accountId};
|
||||||
|
if (!account.homeserver().isEmpty()) {
|
||||||
|
auto accessToken = loadAccessToken(account);
|
||||||
|
if (accessToken.isEmpty()) {
|
||||||
|
// Try to look in the legacy location (QSettings) and if found,
|
||||||
|
// migrate it from there to a file.
|
||||||
|
accessToken = account.accessToken().toLatin1();
|
||||||
|
if (accessToken.isEmpty())
|
||||||
|
continue; // No access token anywhere, no autologin
|
||||||
|
|
||||||
|
saveAccessToken(account, accessToken);
|
||||||
|
account.clearAccessToken(); // Clean the old place
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c = new Connection(account.homeserver(), this);
|
||||||
|
auto deviceName = account.deviceName();
|
||||||
|
connect(c, &Connection::connected, this, [=] {
|
||||||
|
c->loadState();
|
||||||
|
addConnection(c);
|
||||||
|
});
|
||||||
|
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit initiated();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Controller::loadAccessToken(const AccountSettings& account) {
|
||||||
|
QFile accountTokenFile{accessTokenFileName(account)};
|
||||||
|
if (accountTokenFile.open(QFile::ReadOnly)) {
|
||||||
|
if (accountTokenFile.size() < 1024) return accountTokenFile.readAll();
|
||||||
|
|
||||||
|
qWarning() << "File" << accountTokenFile.fileName() << "is"
|
||||||
|
<< accountTokenFile.size()
|
||||||
|
<< "bytes long - too long for a token, ignoring it.";
|
||||||
|
}
|
||||||
|
qWarning() << "Could not open access token file"
|
||||||
|
<< accountTokenFile.fileName();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Controller::saveAccessToken(const AccountSettings& account,
|
||||||
|
const QByteArray& accessToken) {
|
||||||
|
// (Re-)Make a dedicated file for access_token.
|
||||||
|
QFile accountTokenFile{accessTokenFileName(account)};
|
||||||
|
accountTokenFile.remove(); // Just in case
|
||||||
|
|
||||||
|
auto fileDir = QFileInfo(accountTokenFile).dir();
|
||||||
|
if (!((fileDir.exists() || fileDir.mkpath(".")) &&
|
||||||
|
accountTokenFile.open(QFile::WriteOnly))) {
|
||||||
|
emit errorOccured();
|
||||||
} else {
|
} else {
|
||||||
qCritical() << "You are already logged in.";
|
// Try to restrict access rights to the file. The below is useless
|
||||||
|
// on Windows: FAT doesn't control access at all and NTFS is
|
||||||
|
// incompatible with the UNIX perms model used by Qt. If the attempt
|
||||||
|
// didn't have the effect, at least ask the user if it's fine to save
|
||||||
|
// the token to a file readable by others.
|
||||||
|
// TODO: use system-specific API to ensure proper access.
|
||||||
|
if ((accountTokenFile.setPermissions(QFile::ReadOwner |
|
||||||
|
QFile::WriteOwner) &&
|
||||||
|
!(accountTokenFile.permissions() &
|
||||||
|
(QFile::ReadGroup | QFile::ReadOther)))) {
|
||||||
|
accountTokenFile.write(accessToken);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
void Controller::logout() {
|
|
||||||
m_connection->logout();
|
|
||||||
setUserID("");
|
|
||||||
setToken("");
|
|
||||||
setIsLogin(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::connected() {
|
void Controller::joinRoom(Connection* c, const QString& alias) {
|
||||||
setHomeserver(m_connection->homeserver().toString());
|
JoinRoomJob* joinRoomJob = c->joinRoom(alias);
|
||||||
setUserID(m_connection->userId());
|
|
||||||
setToken(m_connection->accessToken());
|
|
||||||
m_connection->loadState();
|
|
||||||
resync();
|
|
||||||
setIsLogin(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::resync() { m_connection->sync(30000); }
|
|
||||||
|
|
||||||
void Controller::reconnect() {
|
|
||||||
qDebug() << "Connection lost. Reconnecting...";
|
|
||||||
m_connection->connectWithToken(m_userID, m_token, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::joinRoom(const QString& alias) {
|
|
||||||
JoinRoomJob* joinRoomJob = m_connection->joinRoom(alias);
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished,
|
joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished,
|
||||||
[=] { setBusy(false); });
|
[=] { setBusy(false); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::createRoom(const QString& name, const QString& topic) {
|
void Controller::createRoom(Connection* c, const QString& name,
|
||||||
|
const QString& topic) {
|
||||||
CreateRoomJob* createRoomJob =
|
CreateRoomJob* createRoomJob =
|
||||||
((Connection*)m_connection)
|
c->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||||
->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
createRoomJob->connect(createRoomJob, &CreateRoomJob::finished,
|
createRoomJob->connect(createRoomJob, &CreateRoomJob::finished,
|
||||||
[=] { setBusy(false); });
|
[=] { setBusy(false); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::createDirectChat(const QString& userID) {
|
|
||||||
m_connection->requestDirectChat(userID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::copyToClipboard(const QString& text) {
|
void Controller::copyToClipboard(const QString& text) {
|
||||||
m_clipboard->setText(text);
|
m_clipboard->setText(text);
|
||||||
}
|
}
|
||||||
|
@ -120,3 +218,16 @@ void Controller::showMessage(const QString& title, const QString& msg,
|
||||||
const QIcon& icon) {
|
const QIcon& icon) {
|
||||||
tray->showMessage(title, msg, icon);
|
tray->showMessage(title, msg, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QImage Controller::safeImage(QImage image) {
|
||||||
|
if (image.isNull()) return QImage();
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor Controller::color(QString userId) {
|
||||||
|
return QColor(SettingsGroup("UI/Color").value(userId, "#498882").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::setColor(QString userId, QColor newColor) {
|
||||||
|
SettingsGroup("UI/Color").setValue(userId, newColor.name());
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define CONTROLLER_H
|
#define CONTROLLER_H
|
||||||
|
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
#include "settings.h"
|
||||||
#include "user.h"
|
#include "user.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
@ -15,101 +16,71 @@ using namespace QMatrixClient;
|
||||||
class Controller : public QObject {
|
class Controller : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(Connection* connection READ connection CONSTANT)
|
|
||||||
Q_PROPERTY(bool isLogin READ isLogin WRITE setIsLogin NOTIFY isLoginChanged)
|
|
||||||
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY
|
|
||||||
homeserverChanged)
|
|
||||||
Q_PROPERTY(QString userID READ userID WRITE setUserID NOTIFY userIDChanged)
|
|
||||||
Q_PROPERTY(QByteArray token READ token WRITE setToken NOTIFY tokenChanged)
|
|
||||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||||
|
Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY
|
||||||
|
connectionDropped)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Controller(QObject* parent = nullptr);
|
explicit Controller(QObject* parent = nullptr);
|
||||||
~Controller();
|
~Controller();
|
||||||
|
|
||||||
// All the Q_INVOKABLEs.
|
// All the Q_INVOKABLEs.
|
||||||
Q_INVOKABLE void login();
|
|
||||||
Q_INVOKABLE void loginWithCredentials(QString, QString, QString);
|
Q_INVOKABLE void loginWithCredentials(QString, QString, QString);
|
||||||
Q_INVOKABLE void logout();
|
|
||||||
|
QVector<Connection*> connections() { return m_connections; }
|
||||||
|
|
||||||
// All the non-Q_INVOKABLE functions.
|
// All the non-Q_INVOKABLE functions.
|
||||||
|
void addConnection(Connection* c);
|
||||||
|
void dropConnection(Connection* c);
|
||||||
|
|
||||||
// All the Q_PROPERTYs.
|
// All the Q_PROPERTYs.
|
||||||
Connection* m_connection = new Connection();
|
|
||||||
Connection* connection() { return m_connection; }
|
|
||||||
|
|
||||||
bool isLogin() { return m_isLogin; }
|
|
||||||
void setIsLogin(bool n) {
|
|
||||||
if (n != m_isLogin) {
|
|
||||||
m_isLogin = n;
|
|
||||||
emit isLoginChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString userID() { return m_userID; }
|
|
||||||
void setUserID(QString n) {
|
|
||||||
if (n != m_userID) {
|
|
||||||
m_userID = n;
|
|
||||||
emit userIDChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray token() { return m_token; }
|
|
||||||
void setToken(QByteArray n) {
|
|
||||||
if (n != m_token) {
|
|
||||||
m_token = n;
|
|
||||||
emit tokenChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString homeserver() { return m_homeserver; }
|
|
||||||
void setHomeserver(QString n) {
|
|
||||||
if (n != m_homeserver) {
|
|
||||||
m_homeserver = n;
|
|
||||||
emit homeserverChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool busy() { return m_busy; }
|
bool busy() { return m_busy; }
|
||||||
void setBusy(bool b) {
|
void setBusy(bool value) {
|
||||||
if (b != m_busy) {
|
if (value != m_busy) {
|
||||||
m_busy = b;
|
m_busy = value;
|
||||||
emit busyChanged();
|
emit busyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int accountCount() { return m_connections.count(); }
|
||||||
|
|
||||||
|
Q_INVOKABLE QColor color(QString userId);
|
||||||
|
Q_INVOKABLE void setColor(QString userId, QColor newColor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QClipboard* m_clipboard = QApplication::clipboard();
|
QClipboard* m_clipboard = QApplication::clipboard();
|
||||||
QSystemTrayIcon* tray = new QSystemTrayIcon();
|
QSystemTrayIcon* tray = new QSystemTrayIcon();
|
||||||
QMenu* trayMenu = new QMenu();
|
QMenu* trayMenu = new QMenu();
|
||||||
|
QVector<Connection*> m_connections;
|
||||||
|
|
||||||
bool m_isLogin = false;
|
|
||||||
QString m_userID;
|
|
||||||
QByteArray m_token;
|
|
||||||
QString m_homeserver;
|
|
||||||
bool m_busy = false;
|
bool m_busy = false;
|
||||||
|
|
||||||
void connected();
|
QByteArray loadAccessToken(const AccountSettings& account);
|
||||||
void resync();
|
bool saveAccessToken(const AccountSettings& account,
|
||||||
void reconnect();
|
const QByteArray& accessToken);
|
||||||
|
void loadSettings();
|
||||||
|
void saveSettings() const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void invokeLogin();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
|
||||||
void isLoginChanged();
|
|
||||||
void userIDChanged();
|
|
||||||
void tokenChanged();
|
|
||||||
void homeserverChanged();
|
|
||||||
void busyChanged();
|
void busyChanged();
|
||||||
void errorOccured();
|
void errorOccured();
|
||||||
void toggleWindow();
|
void toggleWindow();
|
||||||
|
void connectionAdded(Connection* conn);
|
||||||
|
void connectionDropped(Connection* conn);
|
||||||
|
void initiated();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void joinRoom(const QString& alias);
|
void logout(Connection* conn);
|
||||||
void createRoom(const QString& name, const QString& topic);
|
void joinRoom(Connection* c, const QString& alias);
|
||||||
void createDirectChat(const QString& userID);
|
void createRoom(Connection* c, const QString& name, const QString& topic);
|
||||||
void copyToClipboard(const QString& text);
|
void copyToClipboard(const QString& text);
|
||||||
void playAudio(QUrl localFile);
|
void playAudio(QUrl localFile);
|
||||||
void showMessage(const QString& title, const QString& msg, const QIcon& icon);
|
void showMessage(const QString& title, const QString& msg, const QIcon& icon);
|
||||||
|
|
||||||
|
static QImage safeImage(QImage image);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|
|
@ -14,8 +14,12 @@ void ImageItem::paint(QPainter *painter) {
|
||||||
if (m_image.isNull()) {
|
if (m_image.isNull()) {
|
||||||
painter->setPen(Qt::NoPen);
|
painter->setPen(Qt::NoPen);
|
||||||
painter->setBrush(QColor(m_color));
|
painter->setBrush(QColor(m_color));
|
||||||
|
if (m_round)
|
||||||
painter->drawEllipse(0, 0, int(bounding_rect.width()),
|
painter->drawEllipse(0, 0, int(bounding_rect.width()),
|
||||||
int(bounding_rect.height()));
|
int(bounding_rect.height()));
|
||||||
|
else
|
||||||
|
painter->drawRect(0, 0, int(bounding_rect.width()),
|
||||||
|
int(bounding_rect.height()));
|
||||||
painter->setPen(QPen(Qt::white, 2));
|
painter->setPen(QPen(Qt::white, 2));
|
||||||
QFont font;
|
QFont font;
|
||||||
font.setPixelSize(22);
|
font.setPixelSize(22);
|
||||||
|
@ -33,11 +37,13 @@ void ImageItem::paint(QPainter *painter) {
|
||||||
|
|
||||||
QPointF center = bounding_rect.center() - scaled.rect().center();
|
QPointF center = bounding_rect.center() - scaled.rect().center();
|
||||||
|
|
||||||
|
if (m_round) {
|
||||||
QPainterPath clip;
|
QPainterPath clip;
|
||||||
clip.addEllipse(
|
clip.addEllipse(
|
||||||
0, 0, bounding_rect.width(),
|
0, 0, bounding_rect.width(),
|
||||||
bounding_rect.height()); // this is the shape we want to clip to
|
bounding_rect.height()); // this is the shape we want to clip to
|
||||||
painter->setClipPath(clip);
|
painter->setClipPath(clip);
|
||||||
|
}
|
||||||
|
|
||||||
if (center.x() < 0) center.setX(0);
|
if (center.x() < 0) center.setX(0);
|
||||||
if (center.y() < 0) center.setY(0);
|
if (center.y() < 0) center.setY(0);
|
||||||
|
@ -66,3 +72,11 @@ void ImageItem::setDefaultColor(QString color) {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ImageItem::setRound(bool value) {
|
||||||
|
if (m_round != value) {
|
||||||
|
m_round = value;
|
||||||
|
emit roundChanged();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ class ImageItem : public QQuickPaintedItem {
|
||||||
Q_PROPERTY(QString hint READ hint WRITE setHint NOTIFY hintChanged)
|
Q_PROPERTY(QString hint READ hint WRITE setHint NOTIFY hintChanged)
|
||||||
Q_PROPERTY(QString defaultColor READ defaultColor WRITE setDefaultColor NOTIFY
|
Q_PROPERTY(QString defaultColor READ defaultColor WRITE setDefaultColor NOTIFY
|
||||||
defaultColorChanged)
|
defaultColorChanged)
|
||||||
|
Q_PROPERTY(bool round READ round WRITE setRound NOTIFY roundChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ImageItem(QQuickItem *parent = nullptr);
|
ImageItem(QQuickItem *parent = nullptr);
|
||||||
|
@ -28,15 +29,20 @@ class ImageItem : public QQuickPaintedItem {
|
||||||
QString defaultColor() { return m_color; }
|
QString defaultColor() { return m_color; }
|
||||||
void setDefaultColor(QString color);
|
void setDefaultColor(QString color);
|
||||||
|
|
||||||
|
bool round() { return m_round; }
|
||||||
|
void setRound(bool value);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void imageChanged();
|
void imageChanged();
|
||||||
void hintChanged();
|
void hintChanged();
|
||||||
void defaultColorChanged();
|
void defaultColorChanged();
|
||||||
|
void roundChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
QString m_hint;
|
QString m_hint = "H";
|
||||||
QString m_color = "#000000";
|
QString m_color = "#000000";
|
||||||
|
bool m_round = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IMAGEITEM_H
|
#endif // IMAGEITEM_H
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
|
||||||
|
#include "accountlistmodel.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "imageitem.h"
|
#include "imageitem.h"
|
||||||
|
@ -38,6 +39,7 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
qmlRegisterType<ImageItem>("Matrique", 0, 1, "ImageItem");
|
qmlRegisterType<ImageItem>("Matrique", 0, 1, "ImageItem");
|
||||||
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
|
qmlRegisterType<Controller>("Matrique", 0, 1, "Controller");
|
||||||
|
qmlRegisterType<AccountListModel>("Matrique", 0, 1, "AccountListModel");
|
||||||
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
|
qmlRegisterType<RoomListModel>("Matrique", 0, 1, "RoomListModel");
|
||||||
qmlRegisterType<UserListModel>("Matrique", 0, 1, "UserListModel");
|
qmlRegisterType<UserListModel>("Matrique", 0, 1, "UserListModel");
|
||||||
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
|
qmlRegisterType<MessageEventModel>("Matrique", 0, 1, "MessageEventModel");
|
||||||
|
|
|
@ -10,6 +10,7 @@ using namespace QMatrixClient;
|
||||||
|
|
||||||
class MatriqueRoom : public Room {
|
class MatriqueRoom : public Room {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QImage avatar READ getAvatar NOTIFY avatarChanged)
|
||||||
Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged)
|
Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged)
|
||||||
Q_PROPERTY(QString usersTyping READ getUsersTyping NOTIFY typingChanged)
|
Q_PROPERTY(QString usersTyping READ getUsersTyping NOTIFY typingChanged)
|
||||||
Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY
|
Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY
|
||||||
|
@ -18,6 +19,8 @@ class MatriqueRoom : public Room {
|
||||||
explicit MatriqueRoom(Connection* connection, QString roomId,
|
explicit MatriqueRoom(Connection* connection, QString roomId,
|
||||||
JoinState joinState = {});
|
JoinState joinState = {});
|
||||||
|
|
||||||
|
QImage getAvatar() { return avatar(128); }
|
||||||
|
|
||||||
const QString& cachedInput() const { return m_cachedInput; }
|
const QString& cachedInput() const { return m_cachedInput; }
|
||||||
void setCachedInput(const QString& input) {
|
void setCachedInput(const QString& input) {
|
||||||
if (input != m_cachedInput) {
|
if (input != m_cachedInput) {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "matriqueuser.h"
|
||||||
|
|
||||||
|
MatriqueUser::MatriqueUser(QString userId, Connection* connection)
|
||||||
|
: User(userId, connection) {
|
||||||
|
connect(this, &User::avatarChanged, this, &MatriqueUser::inheritedAvatarChanged);
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef MATRIQUEUSER_H
|
||||||
|
#define MATRIQUEUSER_H
|
||||||
|
|
||||||
|
#include "user.h"
|
||||||
|
#include "room.h"
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
|
class MatriqueUser : public User {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QImage avatar READ getAvatar NOTIFY inheritedAvatarChanged)
|
||||||
|
public:
|
||||||
|
MatriqueUser(QString userId, Connection* connection);
|
||||||
|
|
||||||
|
QImage getAvatar() { return avatar(128); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void inheritedAvatarChanged(User* user, const Room* roomContext); // https://bugreports.qt.io/browse/QTBUG-7684
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MATRIQUEUSER_H
|
|
@ -15,6 +15,7 @@
|
||||||
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
roles[EventTypeRole] = "eventType";
|
roles[EventTypeRole] = "eventType";
|
||||||
|
roles[AboveEventTypeRole] = "aboveEventType";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[EventIdRole] = "eventId";
|
||||||
roles[TimeRole] = "time";
|
roles[TimeRole] = "time";
|
||||||
roles[AboveTimeRole] = "aboveTime";
|
roles[AboveTimeRole] = "aboveTime";
|
||||||
|
@ -31,6 +32,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
roles[AnnotationRole] = "annotation";
|
roles[AnnotationRole] = "annotation";
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
roles[PlainTextRole] = "plainText";
|
roles[PlainTextRole] = "plainText";
|
||||||
|
roles[UserMarkerRole] = "userMarker";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +53,6 @@ void MessageEventModel::setRoom(MatriqueRoom* room) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
m_currentRoom->disconnect(this);
|
m_currentRoom->disconnect(this);
|
||||||
qDebug() << "Disconnected from" << m_currentRoom->id();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentRoom = room;
|
m_currentRoom = room;
|
||||||
|
@ -78,7 +79,8 @@ void MessageEventModel::setRoom(MatriqueRoom* room) {
|
||||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() -
|
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() -
|
||||||
biggest + timelineBaseIndex() - 1;
|
biggest + timelineBaseIndex() - 1;
|
||||||
refreshEventRoles(rowBelowInserted,
|
refreshEventRoles(rowBelowInserted,
|
||||||
{AboveAuthorRole, AboveSectionRole});
|
{AboveEventTypeRole, AboveAuthorRole,
|
||||||
|
AboveSectionRole, AboveTimeRole});
|
||||||
}
|
}
|
||||||
for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
|
for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
|
||||||
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
|
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
|
||||||
|
@ -108,7 +110,8 @@ void MessageEventModel::setRoom(MatriqueRoom* room) {
|
||||||
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
|
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
|
||||||
if (timelineBaseIndex() > 0) // Refresh below, see #312
|
if (timelineBaseIndex() > 0) // Refresh below, see #312
|
||||||
refreshEventRoles(timelineBaseIndex() - 1,
|
refreshEventRoles(timelineBaseIndex() - 1,
|
||||||
{AboveAuthorRole, AboveSectionRole});
|
{AboveEventTypeRole, AboveAuthorRole,
|
||||||
|
AboveSectionRole, AboveTimeRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventChanged, this,
|
connect(m_currentRoom, &Room::pendingEventChanged, this,
|
||||||
&MessageEventModel::refreshRow);
|
&MessageEventModel::refreshRow);
|
||||||
|
@ -135,6 +138,11 @@ void MessageEventModel::setRoom(MatriqueRoom* room) {
|
||||||
&MessageEventModel::refreshEvent);
|
&MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
||||||
&MessageEventModel::refreshEvent);
|
&MessageEventModel::refreshEvent);
|
||||||
|
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
||||||
|
[=](User* user, QString fromEventId, QString toEventId) {
|
||||||
|
refreshEventRoles(fromEventId, {UserMarkerRole});
|
||||||
|
refreshEventRoles(toEventId, {UserMarkerRole});
|
||||||
|
});
|
||||||
qDebug() << "Connected to room" << room->id() << "as"
|
qDebug() << "Connected to room" << room->id() << "as"
|
||||||
<< room->localUser()->id();
|
<< room->localUser()->id();
|
||||||
} else
|
} else
|
||||||
|
@ -371,7 +379,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
|
||||||
: tr("self-unbanned");
|
: tr("self-unbanned");
|
||||||
}
|
}
|
||||||
return (e.senderId() != e.userId())
|
return (e.senderId() != e.userId())
|
||||||
? tr("has put %1 out of the room").arg(subjectName)
|
? tr("has kicked %1 from the room").arg(subjectName)
|
||||||
: tr("left the room");
|
: tr("left the room");
|
||||||
case MembershipType::Ban:
|
case MembershipType::Ban:
|
||||||
return (e.senderId() != e.userId())
|
return (e.senderId() != e.userId())
|
||||||
|
@ -470,7 +478,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
|
||||||
: tr("self-unbanned");
|
: tr("self-unbanned");
|
||||||
}
|
}
|
||||||
return (e.senderId() != e.userId())
|
return (e.senderId() != e.userId())
|
||||||
? tr("has put %1 out of the room").arg(subjectName)
|
? tr("has kicked %1 from the room").arg(subjectName)
|
||||||
: tr("left the room");
|
: tr("left the room");
|
||||||
case MembershipType::Ban:
|
case MembershipType::Ban:
|
||||||
return (e.senderId() != e.userId())
|
return (e.senderId() != e.userId())
|
||||||
|
@ -622,14 +630,29 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const {
|
||||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AboveSectionRole || role == AboveAuthorRole ||
|
if (role == UserMarkerRole) {
|
||||||
role == AboveTimeRole)
|
QVariantList variantList;
|
||||||
|
for (User* user : m_currentRoom->usersAtEventId(evt.id())) {
|
||||||
|
if (user == m_currentRoom->localUser()) continue;
|
||||||
|
variantList.append(QVariant::fromValue(user));
|
||||||
|
}
|
||||||
|
return variantList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AboveEventTypeRole || role == AboveSectionRole ||
|
||||||
|
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)
|
if (data(i, SpecialMarksRole) != EventStatus::Hidden) switch (role) {
|
||||||
return data(i, role == AboveSectionRole
|
case AboveEventTypeRole:
|
||||||
? SectionRole
|
return data(i, EventTypeRole);
|
||||||
: role == AboveAuthorRole ? AuthorRole : TimeRole);
|
case AboveSectionRole:
|
||||||
|
return data(i, SectionRole);
|
||||||
|
case AboveAuthorRole:
|
||||||
|
return data(i, AuthorRole);
|
||||||
|
case AboveTimeRole:
|
||||||
|
return data(i, TimeRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
#ifndef MESSAGEEVENTMODEL_H
|
#ifndef MESSAGEEVENTMODEL_H
|
||||||
#define MESSAGEEVENTMODEL_H
|
#define MESSAGEEVENTMODEL_H
|
||||||
|
|
||||||
#include "room.h"
|
|
||||||
#include "matriqueroom.h"
|
#include "matriqueroom.h"
|
||||||
|
#include "room.h"
|
||||||
|
|
||||||
#include <QtCore/QAbstractListModel>
|
#include <QtCore/QAbstractListModel>
|
||||||
|
|
||||||
class MessageEventModel : public QAbstractListModel {
|
class MessageEventModel : public QAbstractListModel {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(MatriqueRoom* room READ getRoom WRITE setRoom NOTIFY roomChanged)
|
||||||
MatriqueRoom* room READ getRoom WRITE setRoom NOTIFY roomChanged)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
EventTypeRole = Qt::UserRole + 1,
|
EventTypeRole = Qt::UserRole + 1,
|
||||||
|
AboveEventTypeRole,
|
||||||
EventIdRole,
|
EventIdRole,
|
||||||
TimeRole,
|
TimeRole,
|
||||||
AboveTimeRole,
|
AboveTimeRole,
|
||||||
|
@ -29,6 +29,7 @@ class MessageEventModel : public QAbstractListModel {
|
||||||
LongOperationRole,
|
LongOperationRole,
|
||||||
AnnotationRole,
|
AnnotationRole,
|
||||||
PlainTextRole,
|
PlainTextRole,
|
||||||
|
UserMarkerRole,
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole,
|
||||||
};
|
};
|
||||||
|
@ -42,7 +43,7 @@ class MessageEventModel : public QAbstractListModel {
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex& index,
|
QVariant data(const QModelIndex& index,
|
||||||
int role = Qt::DisplayRole) const override;
|
int role = Qt::DisplayRole) const override;
|
||||||
QHash<int, QByteArray> roleNames() const;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
int refreshEvent(const QString& eventId);
|
int refreshEvent(const QString& eventId);
|
||||||
|
|
|
@ -14,12 +14,20 @@ RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {}
|
||||||
RoomListModel::~RoomListModel() {}
|
RoomListModel::~RoomListModel() {}
|
||||||
|
|
||||||
void RoomListModel::setConnection(Connection* connection) {
|
void RoomListModel::setConnection(Connection* connection) {
|
||||||
if (!connection && connection == m_connection) return;
|
if (connection == m_connection) return;
|
||||||
|
if (m_connection) m_connection->disconnect(this);
|
||||||
|
if (!connection) {
|
||||||
|
qDebug() << "Removing current connection...";
|
||||||
|
m_connection = nullptr;
|
||||||
|
beginResetModel();
|
||||||
|
m_rooms.clear();
|
||||||
|
endResetModel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
using QMatrixClient::Room;
|
|
||||||
m_connection = connection;
|
m_connection = connection;
|
||||||
|
|
||||||
doResetModel();
|
for (MatriqueRoom* room : m_rooms) room->disconnect(this);
|
||||||
|
|
||||||
connect(connection, &Connection::connected, this,
|
connect(connection, &Connection::connected, this,
|
||||||
&RoomListModel::doResetModel);
|
&RoomListModel::doResetModel);
|
||||||
|
@ -30,6 +38,8 @@ void RoomListModel::setConnection(Connection* connection) {
|
||||||
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom);
|
||||||
connect(connection, &Connection::aboutToDeleteRoom, this,
|
connect(connection, &Connection::aboutToDeleteRoom, this,
|
||||||
&RoomListModel::deleteRoom);
|
&RoomListModel::deleteRoom);
|
||||||
|
|
||||||
|
doResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomListModel::doResetModel() {
|
void RoomListModel::doResetModel() {
|
||||||
|
@ -64,7 +74,7 @@ void RoomListModel::connectRoomSignals(MatriqueRoom* room) {
|
||||||
[=] { refresh(room, {AvatarRole}); });
|
[=] { refresh(room, {AvatarRole}); });
|
||||||
connect(room, &Room::addedMessages, this,
|
connect(room, &Room::addedMessages, this,
|
||||||
[=] { refresh(room, {LastEventRole}); });
|
[=] { refresh(room, {LastEventRole}); });
|
||||||
connect(room, &QMatrixClient::Room::aboutToAddNewMessages, this,
|
connect(room, &Room::aboutToAddNewMessages, this,
|
||||||
[=](QMatrixClient::RoomEventsRange eventsRange) {
|
[=](QMatrixClient::RoomEventsRange eventsRange) {
|
||||||
RoomEvent* event = (eventsRange.end() - 1)->get();
|
RoomEvent* event = (eventsRange.end() - 1)->get();
|
||||||
if (event->isStateEvent()) return;
|
if (event->isStateEvent()) return;
|
||||||
|
@ -139,7 +149,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
||||||
MatriqueRoom* room = m_rooms.at(index.row());
|
MatriqueRoom* room = m_rooms.at(index.row());
|
||||||
if (role == NameRole) return room->displayName();
|
if (role == NameRole) return room->displayName();
|
||||||
if (role == AvatarRole) {
|
if (role == AvatarRole) {
|
||||||
if (room->avatarUrl().toString() != "") return room->avatar(64, 64);
|
if (!room->avatarUrl().isEmpty()) return room->avatar(64, 64);
|
||||||
return QImage();
|
return QImage();
|
||||||
}
|
}
|
||||||
if (role == TopicRole) return room->topic();
|
if (role == TopicRole) return room->topic();
|
||||||
|
|
|
@ -67,8 +67,13 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const {
|
||||||
if (role == NameRole) {
|
if (role == NameRole) {
|
||||||
return user->displayname(m_currentRoom);
|
return user->displayname(m_currentRoom);
|
||||||
}
|
}
|
||||||
|
if (role == UserIDRole) {
|
||||||
|
return user->id();
|
||||||
|
}
|
||||||
if (role == AvatarRole) {
|
if (role == AvatarRole) {
|
||||||
return user->avatarUrl(m_currentRoom);
|
if (!user->avatarUrl(m_currentRoom).isEmpty())
|
||||||
|
return user->avatar(64, m_currentRoom);
|
||||||
|
return QImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
@ -110,7 +115,7 @@ 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, {Qt::DecorationRole});
|
if (context == m_currentRoom) refresh(user, {AvatarRole});
|
||||||
}
|
}
|
||||||
|
|
||||||
int UserListModel::findUserPos(User* user) const {
|
int UserListModel::findUserPos(User* user) const {
|
||||||
|
@ -124,6 +129,7 @@ int UserListModel::findUserPos(const QString& username) const {
|
||||||
QHash<int, QByteArray> UserListModel::roleNames() const {
|
QHash<int, QByteArray> UserListModel::roleNames() const {
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
roles[NameRole] = "name";
|
roles[NameRole] = "name";
|
||||||
|
roles[UserIDRole] = "userId";
|
||||||
roles[AvatarRole] = "avatar";
|
roles[AvatarRole] = "avatar";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,7 @@ 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 {
|
enum EventRoles { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole };
|
||||||
NameRole = Qt::UserRole + 1,
|
|
||||||
AvatarRole
|
|
||||||
};
|
|
||||||
|
|
||||||
using User = QMatrixClient::User;
|
using User = QMatrixClient::User;
|
||||||
|
|
||||||
|
@ -30,8 +27,7 @@ class UserListModel : public QAbstractListModel {
|
||||||
void setRoom(QMatrixClient::Room* room);
|
void setRoom(QMatrixClient::Room* room);
|
||||||
User* userAt(QModelIndex index);
|
User* userAt(QModelIndex index);
|
||||||
|
|
||||||
QVariant data(const QModelIndex& index,
|
QVariant data(const QModelIndex& index, int role = NameRole) const override;
|
||||||
int role = NameRole) const override;
|
|
||||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
Loading…
Reference in New Issue