From 2992804472235391cad6c8214799a7a19ff55672 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 9 Sep 2018 10:12:45 +0800 Subject: [PATCH 01/20] Minimum code to get multiple accounts working. --- matrique.pro | 6 +- qml/Login.qml | 5 - qml/Setting.qml | 94 +++++++++------- qml/main.qml | 78 ++++++++------ src/accountlistmodel.cpp | 71 +++++++++++++ src/accountlistmodel.h | 34 ++++++ src/controller.cpp | 224 ++++++++++++++++++++++++++++++--------- src/controller.h | 75 ++++--------- src/main.cpp | 2 + src/userlistmodel.h | 8 +- 10 files changed, 401 insertions(+), 196 deletions(-) create mode 100644 src/accountlistmodel.cpp create mode 100644 src/accountlistmodel.h diff --git a/matrique.pro b/matrique.pro index 85e6a42..5ba689a 100644 --- a/matrique.pro +++ b/matrique.pro @@ -29,7 +29,8 @@ SOURCES += src/main.cpp \ src/emojimodel.cpp \ src/matriqueroom.cpp \ src/userlistmodel.cpp \ - src/imageitem.cpp + src/imageitem.cpp \ + src/accountlistmodel.cpp RESOURCES += \ res.qrc @@ -89,4 +90,5 @@ HEADERS += \ src/emojimodel.h \ src/matriqueroom.h \ src/userlistmodel.h \ - src/imageitem.h + src/imageitem.h \ + src/accountlistmodel.h diff --git a/qml/Login.qml b/qml/Login.qml index 25b6db9..6ba5c19 100644 --- a/qml/Login.qml +++ b/qml/Login.qml @@ -151,11 +151,6 @@ Page { 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) } } diff --git a/qml/Setting.qml b/qml/Setting.qml index 2fcf080..367d3fb 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -8,52 +8,64 @@ import "component" import "form" Page { - property var connection +// Page { +// id: accountForm +// parent: null - Page { +// padding: 64 + +// ColumnLayout { +// RowLayout { +// Layout.preferredHeight: 60 + +// ImageStatus { +// 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.fillHeight: true + +// Label { +// font.pointSize: 18 +// text: matriqueController.isLogin ? connection.localUser.displayName : "" +// } + +// Label { +// font.pointSize: 12 +// text: matriqueController.isLogin ? connection.localUser.id : "" +// } +// } +// } + +// Button { +// text: "Logout" +// highlighted: true + +// onClicked: { +// matriqueController.logout() +// Qt.quit() +// } +// } +// } +// } + + Page{ id: accountForm + parent: null - padding: 64 +// Button { +// flat: true +// highlighted: true +// text: "Login" - ColumnLayout { - RowLayout { - Layout.preferredHeight: 60 - - ImageStatus { - 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.fillHeight: true - - Label { - font.pointSize: 18 - text: matriqueController.isLogin ? connection.localUser.displayName : "" - } - - Label { - font.pointSize: 12 - text: matriqueController.isLogin ? connection.localUser.id : "" - } - } - } - - Button { - text: "Logout" - highlighted: true - - onClicked: { - matriqueController.logout() - Qt.quit() - } - } - } +// onClicked: stackView.push(loginPage) +// } } Page { diff --git a/qml/main.qml b/qml/main.qml index e72f662..58bd27f 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -25,12 +25,6 @@ ApplicationWindow { Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light - Settings { - property alias homeserver: matriqueController.homeserver - property alias userID: matriqueController.userID - property alias token: matriqueController.token - } - FontLoader { id: materialFont; source: "qrc:/asset/font/material.ttf" } Controller { @@ -77,15 +71,13 @@ ApplicationWindow { parent: null - connection: window.connection + connection: accountListView.currentConnection } Setting { id: settingPage parent: null - - connection: window.connection } RowLayout { @@ -104,25 +96,34 @@ ApplicationWindow { anchors.fill: parent spacing: 0 - SideNavButton { + ListView { + property var currentConnection: null + Layout.fillWidth: true - Layout.preferredHeight: width - - ImageStatus { - anchors.fill: parent - anchors.margins: 12 - - 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 : "" - } - - page: roomPage - } - - Rectangle { Layout.fillHeight: true - color: "transparent" + id: accountListView + + model: AccountListModel { controller: matriqueController } + + spacing: 0 + + delegate: SideNavButton { + width: parent.width + height: width + + ImageStatus { + anchors.fill: parent + anchors.margins: 12 + +// source: matriqueController.isLogin ? connection.localUser && connection.localUser.avatarUrl ? "image://mxc/" + connection.localUser.avatarUrl : "" : "qrc:/asset/img/avatar.png" + displayText: name + } + + page: roomPage + + onClicked: accountListView.currentConnection = connection + } } SideNavButton { @@ -241,7 +242,20 @@ ApplicationWindow { anchors.fill: parent icon: "\ue8b8" - color: parent.highlighted ? Material.accent : "white" + color: "white" + } + page: loginPage + } + + SideNavButton { + Layout.fillWidth: true + Layout.preferredHeight: width + + MaterialIcon { + anchors.fill: parent + + icon: "\ue8b8" + color: "white" } page: settingPage } @@ -272,13 +286,9 @@ ApplicationWindow { } } - Component.onCompleted: { - imageProvider.connection = matriqueController.connection - - if (matriqueController.userID && matriqueController.token) { - matriqueController.login(); - } else { - stackView.replace(loginPage); - } + Binding { + target: imageProvider + property: "connection" + value: matriqueController.connection } } diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp new file mode 100644 index 0000000..e54c0e7 --- /dev/null +++ b/src/accountlistmodel.cpp @@ -0,0 +1,71 @@ +#include "accountlistmodel.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) { + beginInsertRows(QModelIndex(), m_connections.count(), + m_connections.count()); + m_connections.append(conn); + endInsertRows(); + }); + connect(m_controller, &Controller::connectionDropped, this, + [=](Connection* conn) { + 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_controller->connections().count()) { + qDebug() + << "UserListModel, something's wrong: index.row() >= m_users.count()"; + return QVariant(); + } + auto m_connection = m_controller->connections().at(index.row()); + if (role == NameRole) { + return m_connection->user()->displayname(); + } + if (role == AvatarRole) { + return m_connection->user()->avatar(64); + } + if (role == ConnectionRole) { + return QVariant::fromValue(m_connection); + } + + return QVariant(); +} + +int AccountListModel::rowCount(const QModelIndex& parent) const { + if (parent.isValid()) return 0; + + return m_controller->connections().count(); +} + +QHash AccountListModel::roleNames() const { + QHash roles; + roles[NameRole] = "name"; + roles[AvatarRole] = "avatar"; + roles[ConnectionRole] = "connection"; + return roles; +} diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h new file mode 100644 index 0000000..300d742 --- /dev/null +++ b/src/accountlistmodel.h @@ -0,0 +1,34 @@ +#ifndef ACCOUNTLISTMODEL_H +#define ACCOUNTLISTMODEL_H + +#include "controller.h" + +#include +#include + +class AccountListModel : public QAbstractListModel { + Q_OBJECT + Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY + controllerChanged) + public: + enum EventRoles { NameRole = Qt::UserRole + 1, AvatarRole, ConnectionRole }; + + AccountListModel(QObject* parent = nullptr); + + QVariant data(const QModelIndex& index, int role = NameRole) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + + QHash roleNames() const override; + + Controller* controller() { return m_controller; } + void setController(Controller* value); + + private: + Controller* m_controller; + QVector m_connections; + + signals: + void controllerChanged(); +}; + +#endif // ACCOUNTLISTMODEL_H diff --git a/src/controller.cpp b/src/controller.cpp index 8097df8..f031ec7 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,6 +1,7 @@ #include "controller.h" #include "matriqueroom.h" +#include "settings.h" #include "events/eventcontent.h" #include "events/roommessageevent.h" @@ -8,7 +9,21 @@ #include "csapi/joining.h" #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include Controller::Controller(QObject* parent) : QObject(parent) { tray->setIcon(QIcon(":/asset/img/icon.png")); @@ -25,84 +40,187 @@ Controller::Controller(QObject* parent) : QObject(parent) { Connection::setRoomType(); - connect(m_connection, &Connection::connected, this, &Controller::connected); - 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); }); + invokeLogin(); } Controller::~Controller() { - m_connection->saveState(); - m_connection->stopSync(); - m_connection->deleteLater(); -} - -void Controller::login() { - if (!m_isLogin) { - m_connection->setHomeserver(QUrl(m_homeserver)); - m_connection->connectWithToken(m_userID, m_token, ""); - } + // m_connection->saveState(); + // m_connection->stopSync(); + // m_connection->deleteLater(); } void Controller::loginWithCredentials(QString serverAddr, QString user, QString pass) { - if (!m_isLogin) { - if (!user.isEmpty() && !pass.isEmpty()) { - m_connection->setHomeserver(QUrl(serverAddr)); - m_connection->connectToServer(user, pass, ""); - } - } else { - qCritical() << "You are already logged in."; + if (!user.isEmpty() && !pass.isEmpty()) { + Connection* m_connection = new Connection(this); + m_connection->setHomeserver(QUrl(serverAddr)); + m_connection->connectToServer(user, pass, ""); + connect(m_connection, &Connection::connected, [=] { + AccountSettings account(m_connection->userId()); + 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() { - m_connection->logout(); - setUserID(""); - setToken(""); - setIsLogin(false); +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, [=] { + // gotEvents(c); + + // Borrowed the logic from Quiark's code in Tensor to cache not too + // aggressively and not on the first sync. The static variable instance + // is created per-closure, meaning per-connection (which is why this + // code is not in gotEvents() ). + static int counter = 0; + if (++counter % 17 == 2) c->saveState(); + }); + 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); + + Q_ASSERT(!m_connections.contains(c) && !c->syncJob()); + emit connectionAdded(c); + c->deleteLater(); +} + +inline QString accessTokenFileName(const AccountSettings& account) { + QString fileName = account.userId(); + fileName.replace(':', '_'); + return QStandardPaths::writableLocation( + QStandardPaths::AppLocalDataLocation) + + '/' + fileName; +} + +void Controller::invokeLogin() { + using namespace QMatrixClient; + const auto accounts = SettingsGroup("Accounts").childGroups(); + bool autoLoggedIn = false; + 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 + } + + autoLoggedIn = true; + 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()); + } + } +} + +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 { + // 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::connected() { - setHomeserver(m_connection->homeserver().toString()); - setUserID(m_connection->userId()); - setToken(m_connection->accessToken()); - m_connection->loadState(); - resync(); - setIsLogin(true); + // setHomeserver(m_connection->homeserver().toString()); + // setUserID(m_connection->userId()); + // setToken(m_connection->accessToken()); + // m_connection->loadState(); + // resync(); + // setIsLogin(true); } -void Controller::resync() { m_connection->sync(30000); } +void Controller::resync() { /*m_connection->sync(30000);*/ +} void Controller::reconnect() { - qDebug() << "Connection lost. Reconnecting..."; - m_connection->connectWithToken(m_userID, m_token, ""); + // 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); - joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished, - [=] { setBusy(false); }); + // JoinRoomJob* joinRoomJob = m_connection->joinRoom(alias); + // setBusy(true); + // joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished, + // [=] { setBusy(false); }); } void Controller::createRoom(const QString& name, const QString& topic) { - CreateRoomJob* createRoomJob = - ((Connection*)m_connection) - ->createRoom(Connection::PublishRoom, "", name, topic, QStringList()); - setBusy(true); - createRoomJob->connect(createRoomJob, &CreateRoomJob::finished, - [=] { setBusy(false); }); + // CreateRoomJob* createRoomJob = + // ((Connection*)m_connection) + // ->createRoom(Connection::PublishRoom, "", name, topic, + // QStringList()); + // setBusy(true); + // createRoomJob->connect(createRoomJob, &CreateRoomJob::finished, + // [=] { setBusy(false); }); } void Controller::createDirectChat(const QString& userID) { - m_connection->requestDirectChat(userID); + // m_connection->requestDirectChat(userID); } void Controller::copyToClipboard(const QString& text) { diff --git a/src/controller.h b/src/controller.h index 7d05780..55cbd20 100644 --- a/src/controller.h +++ b/src/controller.h @@ -2,6 +2,7 @@ #define CONTROLLER_H #include "connection.h" +#include "settings.h" #include "user.h" #include @@ -15,12 +16,6 @@ using namespace QMatrixClient; class Controller : public QObject { 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) public: @@ -28,80 +23,50 @@ class Controller : public QObject { ~Controller(); // All the Q_INVOKABLEs. - Q_INVOKABLE void login(); Q_INVOKABLE void loginWithCredentials(QString, QString, QString); - Q_INVOKABLE void logout(); + + QVector connections() { return m_connections; } // All the non-Q_INVOKABLE functions. + void addConnection(Connection* c); + void dropConnection(Connection* c); // 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; } - void setBusy(bool b) { - if (b != m_busy) { - m_busy = b; + void setBusy(bool value) { + if (value != m_busy) { + m_busy = value; emit busyChanged(); } } + QVector m_connections; + private: QClipboard* m_clipboard = QApplication::clipboard(); QSystemTrayIcon* tray = new QSystemTrayIcon(); QMenu* trayMenu = new QMenu(); - bool m_isLogin = false; - QString m_userID; - QByteArray m_token; - QString m_homeserver; bool m_busy = false; void connected(); void resync(); void reconnect(); + QByteArray loadAccessToken(const AccountSettings& account); + bool saveAccessToken(const AccountSettings& account, + const QByteArray& accessToken); + void loadSettings(); + void saveSettings() const; + + private slots: + void invokeLogin(); signals: - void connectionChanged(); - void isLoginChanged(); - void userIDChanged(); - void tokenChanged(); - void homeserverChanged(); void busyChanged(); void errorOccured(); void toggleWindow(); + void connectionAdded(Connection* conn); + void connectionDropped(Connection* conn); public slots: void joinRoom(const QString& alias); diff --git a/src/main.cpp b/src/main.cpp index 3636474..9c692dc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include #include +#include "accountlistmodel.h" #include "controller.h" #include "emojimodel.h" #include "imageitem.h" @@ -38,6 +39,7 @@ int main(int argc, char *argv[]) { qmlRegisterType("Matrique", 0, 1, "ImageItem"); qmlRegisterType("Matrique", 0, 1, "Controller"); + qmlRegisterType("Matrique", 0, 1, "AccountListModel"); qmlRegisterType("Matrique", 0, 1, "RoomListModel"); qmlRegisterType("Matrique", 0, 1, "UserListModel"); qmlRegisterType("Matrique", 0, 1, "MessageEventModel"); diff --git a/src/userlistmodel.h b/src/userlistmodel.h index d09adb7..068cfa6 100644 --- a/src/userlistmodel.h +++ b/src/userlistmodel.h @@ -17,10 +17,7 @@ class UserListModel : public QAbstractListModel { Q_PROPERTY( QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged) public: - enum EventRoles { - NameRole = Qt::UserRole + 1, - AvatarRole - }; + enum EventRoles { NameRole = Qt::UserRole + 1, AvatarRole }; using User = QMatrixClient::User; @@ -30,8 +27,7 @@ class UserListModel : public QAbstractListModel { void setRoom(QMatrixClient::Room* room); User* userAt(QModelIndex index); - QVariant data(const QModelIndex& index, - int role = NameRole) const override; + QVariant data(const QModelIndex& index, int role = NameRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QHash roleNames() const override; From 7c426e254b72e3d335c6d260bc55ae0b95d57218 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 9 Sep 2018 18:35:37 +0800 Subject: [PATCH 02/20] Update libqmatrixclient && fix broken avatar && fix broken image provider. --- include/libqmatrixclient | 2 +- qml/component/RoomDrawer.qml | 6 +++--- qml/main.qml | 9 +++++---- src/accountlistmodel.cpp | 17 +++++++++++++++-- src/imageitem.h | 2 +- src/roomlistmodel.cpp | 6 +++--- src/userlistmodel.cpp | 6 ++++-- 7 files changed, 32 insertions(+), 16 deletions(-) diff --git a/include/libqmatrixclient b/include/libqmatrixclient index d9ff200..3dfcd0f 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit d9ff200ff62fb7f5b6b51082dc3979d5454a1bec +Subproject commit 3dfcd0f4f4501dba5925d894b7f0fbc9414549c8 diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index e852bd9..fe3ba39 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -103,12 +103,12 @@ Drawer { anchors.margins: 8 spacing: 12 - ImageStatus { + ImageItem { Layout.preferredWidth: height Layout.fillHeight: true - source: avatar != "" ? "image://mxc/" + avatar : "" - displayText: name + image: avatar + hint: name } Label { diff --git a/qml/main.qml b/qml/main.qml index 58bd27f..419e898 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -112,12 +112,13 @@ ApplicationWindow { width: parent.width height: width - ImageStatus { + ImageItem { anchors.fill: parent anchors.margins: 12 -// source: matriqueController.isLogin ? connection.localUser && connection.localUser.avatarUrl ? "image://mxc/" + connection.localUser.avatarUrl : "" : "qrc:/asset/img/avatar.png" - displayText: name + hint: name + image: avatar + defaultColor: Material.accent } page: roomPage @@ -289,6 +290,6 @@ ApplicationWindow { Binding { target: imageProvider property: "connection" - value: matriqueController.connection + value: accountListView.currentConnection } } diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index e54c0e7..35ba133 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -1,5 +1,7 @@ #include "accountlistmodel.h" +#include "room.h" + AccountListModel::AccountListModel(QObject* parent) : QAbstractListModel(parent) {} @@ -10,7 +12,18 @@ void AccountListModel::setController(Controller* value) { m_controller = value; - for (auto c : m_controller->connections()) m_connections.append(c); + for (auto c : m_controller->connections()) { + connect(c->user(), &User::avatarChanged, [=] { + const auto it = + std::find(m_connections.begin(), m_connections.end(), c); + if (it == m_connections.end()) { + return; + } + const auto idx = index(it - m_connections.begin()); + emit dataChanged(idx, idx, {AvatarRole}); + }); + m_connections.append(c); + }; connect(m_controller, &Controller::connectionAdded, this, [=](Connection* conn) { @@ -59,7 +72,7 @@ QVariant AccountListModel::data(const QModelIndex& index, int role) const { int AccountListModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; - return m_controller->connections().count(); + return m_connections.count(); } QHash AccountListModel::roleNames() const { diff --git a/src/imageitem.h b/src/imageitem.h index b743f0e..cda0471 100644 --- a/src/imageitem.h +++ b/src/imageitem.h @@ -35,7 +35,7 @@ class ImageItem : public QQuickPaintedItem { private: QImage m_image; - QString m_hint; + QString m_hint = "H"; QString m_color = "#000000"; }; diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 5fa027a..7623d9b 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -19,8 +19,6 @@ void RoomListModel::setConnection(Connection* connection) { using QMatrixClient::Room; m_connection = connection; - doResetModel(); - connect(connection, &Connection::connected, this, &RoomListModel::doResetModel); connect(connection, &Connection::invitedRoom, this, @@ -30,6 +28,8 @@ void RoomListModel::setConnection(Connection* connection) { connect(connection, &Connection::leftRoom, this, &RoomListModel::updateRoom); connect(connection, &Connection::aboutToDeleteRoom, this, &RoomListModel::deleteRoom); + + doResetModel(); } void RoomListModel::doResetModel() { @@ -139,7 +139,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const { MatriqueRoom* room = m_rooms.at(index.row()); if (role == NameRole) return room->displayName(); if (role == AvatarRole) { - if (room->avatarUrl().toString() != "") return room->avatar(64, 64); + if (!room->avatarUrl().isEmpty()) return room->avatar(64, 64); return QImage(); } if (role == TopicRole) return room->topic(); diff --git a/src/userlistmodel.cpp b/src/userlistmodel.cpp index 7157196..eac3523 100644 --- a/src/userlistmodel.cpp +++ b/src/userlistmodel.cpp @@ -68,7 +68,9 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { return user->displayname(m_currentRoom); } if (role == AvatarRole) { - return user->avatarUrl(m_currentRoom); + if (!user->avatarUrl(m_currentRoom).isEmpty()) + return user->avatar(32, m_currentRoom); + return QImage(); } return QVariant(); @@ -110,7 +112,7 @@ void UserListModel::refresh(QMatrixClient::User* user, QVector roles) { void UserListModel::avatarChanged(QMatrixClient::User* user, 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 { From 5c55856df3b152791eb011fc0b8fb55ade0a0520 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Sun, 9 Sep 2018 21:13:43 +0800 Subject: [PATCH 03/20] Fix crashing when logging out. That's a complex issue. Yay! --- qml/Setting.qml | 75 +++++++++----------------------- qml/main.qml | 23 ++++++---- src/accountlistmodel.cpp | 20 +++++++-- src/accountlistmodel.h | 7 ++- src/controller.cpp | 91 +++++++++++++++------------------------ src/controller.h | 9 ++-- src/messageeventmodel.cpp | 1 - src/roomlistmodel.cpp | 11 ++++- 8 files changed, 105 insertions(+), 132 deletions(-) diff --git a/qml/Setting.qml b/qml/Setting.qml index 367d3fb..84c07ae 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -2,70 +2,37 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.3 +import Matrique 0.1 import Matrique.Settings 0.1 import "component" import "form" Page { -// Page { -// id: accountForm -// parent: null - -// padding: 64 - -// ColumnLayout { -// RowLayout { -// Layout.preferredHeight: 60 - -// ImageStatus { -// 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.fillHeight: true - -// Label { -// font.pointSize: 18 -// text: matriqueController.isLogin ? connection.localUser.displayName : "" -// } - -// Label { -// font.pointSize: 12 -// text: matriqueController.isLogin ? connection.localUser.id : "" -// } -// } -// } - -// Button { -// text: "Logout" -// highlighted: true - -// onClicked: { -// matriqueController.logout() -// Qt.quit() -// } -// } -// } -// } - - Page{ + property alias listModel: accountSettingsListView.model + Page { id: accountForm - parent: null -// Button { -// flat: true -// highlighted: true -// text: "Login" + padding: 64 -// onClicked: stackView.push(loginPage) -// } + ListView { + anchors.fill: parent + + id: accountSettingsListView + + spacing: 0 + + delegate: RowLayout{ + Label { + text: accountID + } + ItemDelegate { + text: "Logout" + onClicked: matriqueController.logout(connection); + } + } + } } Page { diff --git a/qml/main.qml b/qml/main.qml index 419e898..2f0babc 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -11,7 +11,7 @@ import "component" import "form" ApplicationWindow { - readonly property var connection: matriqueController.connection + readonly property var currentConnection: accountListView.currentConnection ? accountListView.currentConnection : null width: 960 height: 640 @@ -42,6 +42,11 @@ ApplicationWindow { } } + AccountListModel { + id: accountListModel + controller: matriqueController + } + Popup { property bool busy: matriqueController.busy @@ -71,13 +76,15 @@ ApplicationWindow { parent: null - connection: accountListView.currentConnection + connection: currentConnection } Setting { id: settingPage parent: null + + listModel: accountListModel } RowLayout { @@ -104,7 +111,7 @@ ApplicationWindow { id: accountListView - model: AccountListModel { controller: matriqueController } + model: accountListModel spacing: 0 @@ -176,7 +183,7 @@ ApplicationWindow { } } - onAccepted: matriqueController.createRoom(addRoomDialogNameTextField.text, addRoomDialogTopicTextField.text) + onAccepted: matriqueController.createRoom(currentConnection, addRoomDialogNameTextField.text, addRoomDialogTopicTextField.text) } } MenuItem { @@ -202,7 +209,7 @@ ApplicationWindow { placeholderText: "#matrix:matrix.org" } - onAccepted: matriqueController.joinRoom(joinRoomDialogTextField.text) + onAccepted: matriqueController.joinRoom(currentConnection, joinRoomDialogTextField.text) } } @@ -229,7 +236,7 @@ ApplicationWindow { placeholderText: "@bot:matrix.org" } - onAccepted: matriqueController.createDirectChat(directChatDialogTextField.text) + onAccepted: currentConnection.createDirectChat(directChatDialogTextField.text) } } } @@ -283,13 +290,13 @@ ApplicationWindow { id: stackView - initialItem: roomPage + // initialItem: roomPage } } Binding { target: imageProvider property: "connection" - value: accountListView.currentConnection + value: currentConnection } } diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index 35ba133..70f7d5f 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -27,6 +27,8 @@ void AccountListModel::setController(Controller* value) { connect(m_controller, &Controller::connectionAdded, this, [=](Connection* conn) { + if (!conn) { + } beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count()); m_connections.append(conn); @@ -34,6 +36,12 @@ void AccountListModel::setController(Controller* value) { }); 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()) @@ -50,15 +58,18 @@ void AccountListModel::setController(Controller* value) { QVariant AccountListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); - if (index.row() >= m_controller->connections().count()) { - qDebug() - << "UserListModel, something's wrong: index.row() >= m_users.count()"; + if (index.row() >= m_connections.count()) { + qDebug() << "AccountListModel, something's wrong: index.row() >= " + "m_users.count()"; return QVariant(); } - auto m_connection = m_controller->connections().at(index.row()); + auto m_connection = m_connections.at(index.row()); if (role == NameRole) { return m_connection->user()->displayname(); } + if (role == AccountIDRole) { + return m_connection->user()->id(); + } if (role == AvatarRole) { return m_connection->user()->avatar(64); } @@ -78,6 +89,7 @@ int AccountListModel::rowCount(const QModelIndex& parent) const { QHash AccountListModel::roleNames() const { QHash roles; roles[NameRole] = "name"; + roles[AccountIDRole] = "accountID"; roles[AvatarRole] = "avatar"; roles[ConnectionRole] = "connection"; return roles; diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h index 300d742..ddf7fb4 100644 --- a/src/accountlistmodel.h +++ b/src/accountlistmodel.h @@ -11,7 +11,12 @@ class AccountListModel : public QAbstractListModel { Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY controllerChanged) public: - enum EventRoles { NameRole = Qt::UserRole + 1, AvatarRole, ConnectionRole }; + enum EventRoles { + NameRole = Qt::UserRole + 1, + AccountIDRole, + AvatarRole, + ConnectionRole + }; AccountListModel(QObject* parent = nullptr); diff --git a/src/controller.cpp b/src/controller.cpp index f031ec7..8b64f3b 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -40,13 +40,17 @@ Controller::Controller(QObject* parent) : QObject(parent) { Connection::setRoomType(); - invokeLogin(); + QTimer::singleShot(0, this, SLOT(invokeLogin())); } -Controller::~Controller() { - // m_connection->saveState(); - // m_connection->stopSync(); - // m_connection->deleteLater(); +Controller::~Controller() {} + +inline QString accessTokenFileName(const AccountSettings& account) { + QString fileName = account.userId(); + fileName.replace(':', '_'); + return QStandardPaths::writableLocation( + QStandardPaths::AppLocalDataLocation) + + '/' + fileName; } void Controller::loginWithCredentials(QString serverAddr, QString user, @@ -70,18 +74,24 @@ void Controller::loginWithCredentials(QString serverAddr, QString user, } } +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, [=] { - // gotEvents(c); - - // Borrowed the logic from Quiark's code in Tensor to cache not too - // aggressively and not on the first sync. The static variable instance - // is created per-closure, meaning per-connection (which is why this - // code is not in gotEvents() ). static int counter = 0; if (++counter % 17 == 2) c->saveState(); }); @@ -98,23 +108,13 @@ void Controller::dropConnection(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection"); m_connections.removeOne(c); - Q_ASSERT(!m_connections.contains(c) && !c->syncJob()); - emit connectionAdded(c); + emit connectionDropped(c); c->deleteLater(); } -inline QString accessTokenFileName(const AccountSettings& account) { - QString fileName = account.userId(); - fileName.replace(':', '_'); - return QStandardPaths::writableLocation( - QStandardPaths::AppLocalDataLocation) + - '/' + fileName; -} - void Controller::invokeLogin() { using namespace QMatrixClient; const auto accounts = SettingsGroup("Accounts").childGroups(); - bool autoLoggedIn = false; for (const auto& accountId : accounts) { AccountSettings account{accountId}; if (!account.homeserver().isEmpty()) { @@ -130,7 +130,6 @@ void Controller::invokeLogin() { account.clearAccessToken(); // Clean the old place } - autoLoggedIn = true; auto c = new Connection(account.homeserver(), this); auto deviceName = account.deviceName(); connect(c, &Connection::connected, this, [=] { @@ -185,42 +184,20 @@ bool Controller::saveAccessToken(const AccountSettings& account, return false; } -void Controller::connected() { - // setHomeserver(m_connection->homeserver().toString()); - // setUserID(m_connection->userId()); - // setToken(m_connection->accessToken()); - // m_connection->loadState(); - // resync(); - // setIsLogin(true); +void Controller::joinRoom(Connection* c, const QString& alias) { + JoinRoomJob* joinRoomJob = c->joinRoom(alias); + setBusy(true); + joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished, + [=] { setBusy(false); }); } -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); - // joinRoomJob->connect(joinRoomJob, &JoinRoomJob::finished, - // [=] { setBusy(false); }); -} - -void Controller::createRoom(const QString& name, const QString& topic) { - // CreateRoomJob* createRoomJob = - // ((Connection*)m_connection) - // ->createRoom(Connection::PublishRoom, "", name, topic, - // QStringList()); - // setBusy(true); - // createRoomJob->connect(createRoomJob, &CreateRoomJob::finished, - // [=] { setBusy(false); }); -} - -void Controller::createDirectChat(const QString& userID) { - // m_connection->requestDirectChat(userID); +void Controller::createRoom(Connection* c, const QString& name, + const QString& topic) { + CreateRoomJob* createRoomJob = + c->createRoom(Connection::PublishRoom, "", name, topic, QStringList()); + setBusy(true); + createRoomJob->connect(createRoomJob, &CreateRoomJob::finished, + [=] { setBusy(false); }); } void Controller::copyToClipboard(const QString& text) { diff --git a/src/controller.h b/src/controller.h index 55cbd20..46ae4f5 100644 --- a/src/controller.h +++ b/src/controller.h @@ -49,9 +49,6 @@ class Controller : public QObject { bool m_busy = false; - void connected(); - void resync(); - void reconnect(); QByteArray loadAccessToken(const AccountSettings& account); bool saveAccessToken(const AccountSettings& account, const QByteArray& accessToken); @@ -69,9 +66,9 @@ class Controller : public QObject { void connectionDropped(Connection* conn); public slots: - void joinRoom(const QString& alias); - void createRoom(const QString& name, const QString& topic); - void createDirectChat(const QString& userID); + void logout(Connection* conn); + void joinRoom(Connection* c, const QString& alias); + void createRoom(Connection* c, const QString& name, const QString& topic); void copyToClipboard(const QString& text); void playAudio(QUrl localFile); void showMessage(const QString& title, const QString& msg, const QIcon& icon); diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index d7d1446..f512249 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -51,7 +51,6 @@ void MessageEventModel::setRoom(MatriqueRoom* room) { beginResetModel(); if (m_currentRoom) { m_currentRoom->disconnect(this); - qDebug() << "Disconnected from" << m_currentRoom->id(); } m_currentRoom = room; diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 7623d9b..6959bad 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -14,7 +14,16 @@ RoomListModel::RoomListModel(QObject* parent) : QAbstractListModel(parent) {} RoomListModel::~RoomListModel() {} void RoomListModel::setConnection(Connection* connection) { - if (!connection && connection == m_connection) return; + if (connection == m_connection) return; + if (!connection) { + qDebug() << "Removing current connection..."; + m_connection->disconnect(this); + m_connection = nullptr; + beginResetModel(); + m_rooms.clear(); + endResetModel(); + return; + } using QMatrixClient::Room; m_connection = connection; From f5b24f32b8b1c8902c3c9bc907c492037891aa48 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 07:03:43 +0800 Subject: [PATCH 04/20] Fix avatar issue in accountlistmodel. --- src/accountlistmodel.cpp | 26 ++++++++++++++++---------- src/accountlistmodel.h | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index 70f7d5f..a66c40d 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -13,15 +13,7 @@ void AccountListModel::setController(Controller* value) { m_controller = value; for (auto c : m_controller->connections()) { - connect(c->user(), &User::avatarChanged, [=] { - const auto it = - std::find(m_connections.begin(), m_connections.end(), c); - if (it == m_connections.end()) { - return; - } - const auto idx = index(it - m_connections.begin()); - emit dataChanged(idx, idx, {AvatarRole}); - }); + connectConnectionSignals(c); m_connections.append(c); }; @@ -31,6 +23,7 @@ void AccountListModel::setController(Controller* value) { } beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count()); + connectConnectionSignals(conn); m_connections.append(conn); endInsertRows(); }); @@ -71,7 +64,9 @@ QVariant AccountListModel::data(const QModelIndex& index, int role) const { return m_connection->user()->id(); } if (role == AvatarRole) { - return m_connection->user()->avatar(64); + if (!m_connection->user()->avatarUrl().isEmpty()) + return m_connection->user()->avatar(64); + return QImage(); } if (role == ConnectionRole) { return QVariant::fromValue(m_connection); @@ -86,6 +81,17 @@ int AccountListModel::rowCount(const QModelIndex& parent) const { return m_connections.count(); } +void AccountListModel::connectConnectionSignals(Connection* conn) { + connect(conn->user(), &User::avatarChanged, [=] { + const auto it = std::find(m_connections.begin(), m_connections.end(), conn); + if (it == m_connections.end()) { + return; + } + const auto idx = index(it - m_connections.begin()); + emit dataChanged(idx, idx, {AvatarRole}); + }); +} + QHash AccountListModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h index ddf7fb4..e8a8061 100644 --- a/src/accountlistmodel.h +++ b/src/accountlistmodel.h @@ -32,6 +32,7 @@ class AccountListModel : public QAbstractListModel { Controller* m_controller; QVector m_connections; + void connectConnectionSignals(Connection* conn); signals: void controllerChanged(); }; From 13a8d6b88950a725fe5d365d439c4824d2b5f300 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 08:06:32 +0800 Subject: [PATCH 05/20] Fix notification issue. --- js/md.js | 39 +-------------------------------- js/util.js | 25 +++++++++++++++++++++ qml/component/RoomDrawer.qml | 13 ++++++----- qml/component/SideNavButton.qml | 13 +++-------- qml/form/RoomListForm.qml | 24 ++------------------ qml/main.qml | 2 +- res.qrc | 1 + src/accountlistmodel.cpp | 2 +- src/controller.cpp | 4 ++-- src/roomlistmodel.cpp | 7 +++--- 10 files changed, 47 insertions(+), 83 deletions(-) create mode 100644 js/util.js diff --git a/js/md.js b/js/md.js index d8155ea..ceae2ef 100644 --- a/js/md.js +++ b/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 - * @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}; -/** - * 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 - * @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 rules = [ diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..56d75da --- /dev/null +++ b/js/util.js @@ -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.clear() + stack.push(page) + } + } +} diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index fe3ba39..2239150 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -4,6 +4,8 @@ import QtQuick.Controls.Material 2.2 import QtQuick.Layouts 1.3 import Matrique 0.1 +import "qrc:/js/util.js" as Util + Drawer { property var room @@ -94,6 +96,10 @@ Drawer { boundsBehavior: Flickable.DragOverBounds + model: UserListModel { + room: roomDrawer.room + } + delegate: ItemDelegate { width: parent.width height: 48 @@ -107,6 +113,7 @@ Drawer { Layout.preferredWidth: height Layout.fillHeight: true + defaultColor: Util.stringToColor(name) image: avatar hint: name } @@ -119,12 +126,6 @@ Drawer { } } - model: UserListModel { - id: userListModel - - room: roomDrawer.room - } - ScrollBar.vertical: ScrollBar {} } diff --git a/qml/component/SideNavButton.qml b/qml/component/SideNavButton.qml index 8c9bf27..9ec0978 100644 --- a/qml/component/SideNavButton.qml +++ b/qml/component/SideNavButton.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 +import "qrc:/js/util.js" as Util + ItemDelegate { property var page readonly property bool selected: stackView.currentItem === page @@ -18,14 +20,5 @@ ItemDelegate { } } - onClicked: { - if(page && stackView.currentItem !== page) { - if(stackView.depth === 1) { - stackView.replace(page) - } else { - stackView.clear() - stackView.push(page) - } - } - } + onClicked: Util.pushToStack(stackView, page) } diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index 5af52d3..d134125 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -9,6 +9,7 @@ import SortFilterProxyModel 0.2 import Matrique.Settings 0.1 import "../component" +import "qrc:/js/util.js" as Util Item { property alias listModel: roomListProxyModel.sourceModel @@ -142,14 +143,6 @@ Item { spacing: 12 - // ImageStatus { - // Layout.preferredWidth: height - // Layout.fillHeight: true - - // source: avatar ? "image://mxc/" + avatar : "" - // displayText: name - // } - ImageItem { id: imageItem @@ -157,7 +150,7 @@ Item { Layout.fillHeight: true hint: name || "No Name" - defaultColor: stringToColor(name || "No Name") + defaultColor: Util.stringToColor(name || "No Name") image: avatar } @@ -224,17 +217,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; - } } diff --git a/qml/main.qml b/qml/main.qml index 2f0babc..b0dd9c1 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -290,7 +290,7 @@ ApplicationWindow { id: stackView - // initialItem: roomPage + initialItem: roomPage } } diff --git a/res.qrc b/res.qrc index 300bca8..cebc6a2 100644 --- a/res.qrc +++ b/res.qrc @@ -30,5 +30,6 @@ qml/component/StateDelegate.qml qml/component/AutoLabel.qml qml/component/RoomDrawer.qml + js/util.js diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index a66c40d..d9868c1 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -82,7 +82,7 @@ int AccountListModel::rowCount(const QModelIndex& parent) const { } void AccountListModel::connectConnectionSignals(Connection* conn) { - connect(conn->user(), &User::avatarChanged, [=] { + connect(conn->user(), &User::avatarChanged, this, [=] { const auto it = std::find(m_connections.begin(), m_connections.end(), conn); if (it == m_connections.end()) { return; diff --git a/src/controller.cpp b/src/controller.cpp index 8b64f3b..cb3310e 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -92,8 +92,8 @@ void Controller::addConnection(Connection* c) { m_connections.push_back(c); connect(c, &Connection::syncDone, this, [=] { - static int counter = 0; - if (++counter % 17 == 2) c->saveState(); + c->saveState(); + c->sync(30000); }); connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); }); diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 6959bad..3ab5b56 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -15,9 +15,9 @@ RoomListModel::~RoomListModel() {} void RoomListModel::setConnection(Connection* connection) { if (connection == m_connection) return; + m_connection->disconnect(this); if (!connection) { qDebug() << "Removing current connection..."; - m_connection->disconnect(this); m_connection = nullptr; beginResetModel(); m_rooms.clear(); @@ -25,9 +25,10 @@ void RoomListModel::setConnection(Connection* connection) { return; } - using QMatrixClient::Room; m_connection = connection; + for (MatriqueRoom* room : m_rooms) room->disconnect(this); + connect(connection, &Connection::connected, this, &RoomListModel::doResetModel); connect(connection, &Connection::invitedRoom, this, @@ -73,7 +74,7 @@ void RoomListModel::connectRoomSignals(MatriqueRoom* room) { [=] { refresh(room, {AvatarRole}); }); connect(room, &Room::addedMessages, this, [=] { refresh(room, {LastEventRole}); }); - connect(room, &QMatrixClient::Room::aboutToAddNewMessages, this, + connect(room, &Room::aboutToAddNewMessages, this, [=](QMatrixClient::RoomEventsRange eventsRange) { RoomEvent* event = (eventsRange.end() - 1)->get(); if (event->isStateEvent()) return; From 0f3d7db0d1060a283cbbeaf20738b63db510fae0 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 09:51:02 +0800 Subject: [PATCH 06/20] Use ImageItem in MessageDelegate. --- matrique.pro | 6 ++++-- qml/component/MessageDelegate.qml | 13 ++++++++++--- qml/component/RoomDrawer.qml | 7 ++++--- qml/form/RoomForm.qml | 8 +++++--- src/controller.cpp | 7 +++++++ src/controller.h | 2 ++ src/imageitem.cpp | 28 +++++++++++++++++++++------- src/imageitem.h | 6 ++++++ src/matriqueroom.h | 3 +++ src/matriqueuser.cpp | 6 ++++++ src/matriqueuser.h | 23 +++++++++++++++++++++++ 11 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 src/matriqueuser.cpp create mode 100644 src/matriqueuser.h diff --git a/matrique.pro b/matrique.pro index 5ba689a..4111400 100644 --- a/matrique.pro +++ b/matrique.pro @@ -30,7 +30,8 @@ SOURCES += src/main.cpp \ src/matriqueroom.cpp \ src/userlistmodel.cpp \ src/imageitem.cpp \ - src/accountlistmodel.cpp + src/accountlistmodel.cpp \ + src/matriqueuser.cpp RESOURCES += \ res.qrc @@ -91,4 +92,5 @@ HEADERS += \ src/matriqueroom.h \ src/userlistmodel.h \ src/imageitem.h \ - src/accountlistmodel.h + src/accountlistmodel.h \ + src/matriqueuser.h diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index 60f1c30..5d6b26c 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -4,6 +4,7 @@ import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 import Matrique 0.1 import Matrique.Settings 0.1 +import "qrc:/js/util.js" as Util RowLayout { readonly property bool avatarVisible: !(sentByMe || (aboveAuthor === author && section === aboveSection)) @@ -22,15 +23,16 @@ RowLayout { spacing: 6 - ImageStatus { + ImageItem { Layout.preferredWidth: 40 Layout.preferredHeight: 40 Layout.alignment: Qt.AlignTop round: false visible: avatarVisible - source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : null - displayText: author.displayName + hint: author.displayName + defaultColor: Util.stringToColor(author.displayName) + image: author.avatar } Rectangle { @@ -61,6 +63,11 @@ RowLayout { Material.foreground: Material.accent coloredBackground: highlighted font.bold: true + + MouseArea { + anchors.fill: parent + onClicked: inputField.insert(inputField.cursorPosition, author.displayName) + } } AutoLabel { diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index 2239150..a776444 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -24,13 +24,14 @@ Drawer { anchors.fill: parent anchors.margins: 32 - ImageStatus { + ImageItem { Layout.preferredWidth: 64 Layout.preferredHeight: 64 Layout.alignment: Qt.AlignHCenter - source: room && room.avatarUrl != "" ? "image://mxc/" + room.avatarUrl : null - displayText: room ? room.displayName : "" + hint: room ? room.displayName : "No name" + defaultColor: Util.stringToColor(room ? room.displayName : "No name") + image: matriqueController.safeImage(room ? room.avatar : null) } Label { diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 91741e8..57bf62c 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -9,6 +9,7 @@ import Matrique.Settings 0.1 import "../component" import "qrc:/js/md.js" as Markdown +import "qrc:/js/util.js" as Util Item { property var currentRoom: null @@ -57,12 +58,13 @@ Item { spacing: 12 - ImageStatus { + ImageItem { Layout.preferredWidth: height Layout.fillHeight: true - source: currentRoom && currentRoom.avatarUrl != "" ? "image://mxc/" + currentRoom.avatarUrl : null - displayText: currentRoom ? currentRoom.displayName : "" + hint: currentRoom ? currentRoom.displayName : "No name" + defaultColor: Util.stringToColor(currentRoom ? currentRoom.displayName : "No name") + image: matriqueController.safeImage(currentRoom ? currentRoom.avatar : null) } ColumnLayout { diff --git a/src/controller.cpp b/src/controller.cpp index cb3310e..4c03435 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -1,6 +1,7 @@ #include "controller.h" #include "matriqueroom.h" +#include "matriqueuser.h" #include "settings.h" #include "events/eventcontent.h" @@ -39,6 +40,7 @@ Controller::Controller(QObject* parent) : QObject(parent) { tray->show(); Connection::setRoomType(); + Connection::setUserType(); QTimer::singleShot(0, this, SLOT(invokeLogin())); } @@ -215,3 +217,8 @@ void Controller::showMessage(const QString& title, const QString& msg, const QIcon& icon) { tray->showMessage(title, msg, icon); } + +QImage Controller::safeImage(QImage image) { + if (image.isNull()) return QImage(); + return image; +} diff --git a/src/controller.h b/src/controller.h index 46ae4f5..e42bd37 100644 --- a/src/controller.h +++ b/src/controller.h @@ -72,6 +72,8 @@ class Controller : public QObject { void copyToClipboard(const QString& text); void playAudio(QUrl localFile); void showMessage(const QString& title, const QString& msg, const QIcon& icon); + + static QImage safeImage(QImage image); }; #endif // CONTROLLER_H diff --git a/src/imageitem.cpp b/src/imageitem.cpp index bff488c..69a8acc 100644 --- a/src/imageitem.cpp +++ b/src/imageitem.cpp @@ -14,8 +14,12 @@ void ImageItem::paint(QPainter *painter) { if (m_image.isNull()) { painter->setPen(Qt::NoPen); painter->setBrush(QColor(m_color)); - painter->drawEllipse(0, 0, int(bounding_rect.width()), - int(bounding_rect.height())); + if (m_round) + painter->drawEllipse(0, 0, int(bounding_rect.width()), + int(bounding_rect.height())); + else + painter->drawRect(0, 0, int(bounding_rect.width()), + int(bounding_rect.height())); painter->setPen(QPen(Qt::white, 2)); QFont font; font.setPixelSize(22); @@ -33,11 +37,13 @@ void ImageItem::paint(QPainter *painter) { QPointF center = bounding_rect.center() - scaled.rect().center(); - QPainterPath clip; - clip.addEllipse( - 0, 0, bounding_rect.width(), - bounding_rect.height()); // this is the shape we want to clip to - painter->setClipPath(clip); + if (m_round) { + QPainterPath clip; + clip.addEllipse( + 0, 0, bounding_rect.width(), + bounding_rect.height()); // this is the shape we want to clip to + painter->setClipPath(clip); + } if (center.x() < 0) center.setX(0); if (center.y() < 0) center.setY(0); @@ -66,3 +72,11 @@ void ImageItem::setDefaultColor(QString color) { update(); } } + +void ImageItem::setRound(bool value) { + if (m_round != value) { + m_round = value; + emit roundChanged(); + update(); + } +} diff --git a/src/imageitem.h b/src/imageitem.h index cda0471..0d7cb39 100644 --- a/src/imageitem.h +++ b/src/imageitem.h @@ -13,6 +13,7 @@ class ImageItem : public QQuickPaintedItem { Q_PROPERTY(QString hint READ hint WRITE setHint NOTIFY hintChanged) Q_PROPERTY(QString defaultColor READ defaultColor WRITE setDefaultColor NOTIFY defaultColorChanged) + Q_PROPERTY(bool round READ round WRITE setRound NOTIFY roundChanged) public: ImageItem(QQuickItem *parent = nullptr); @@ -28,15 +29,20 @@ class ImageItem : public QQuickPaintedItem { QString defaultColor() { return m_color; } void setDefaultColor(QString color); + bool round() { return m_round; } + void setRound(bool value); + signals: void imageChanged(); void hintChanged(); void defaultColorChanged(); + void roundChanged(); private: QImage m_image; QString m_hint = "H"; QString m_color = "#000000"; + bool m_round = true; }; #endif // IMAGEITEM_H diff --git a/src/matriqueroom.h b/src/matriqueroom.h index eb0f1bf..8250ca5 100644 --- a/src/matriqueroom.h +++ b/src/matriqueroom.h @@ -10,6 +10,7 @@ using namespace QMatrixClient; class MatriqueRoom : public Room { Q_OBJECT + Q_PROPERTY(QImage avatar READ getAvatar NOTIFY avatarChanged) Q_PROPERTY(bool hasUsersTyping READ hasUsersTyping NOTIFY typingChanged) Q_PROPERTY(QString usersTyping READ getUsersTyping NOTIFY typingChanged) Q_PROPERTY(QString cachedInput READ cachedInput WRITE setCachedInput NOTIFY @@ -18,6 +19,8 @@ class MatriqueRoom : public Room { explicit MatriqueRoom(Connection* connection, QString roomId, JoinState joinState = {}); + QImage getAvatar() { return avatar(64); } + const QString& cachedInput() const { return m_cachedInput; } void setCachedInput(const QString& input) { if (input != m_cachedInput) { diff --git a/src/matriqueuser.cpp b/src/matriqueuser.cpp new file mode 100644 index 0000000..a8bf365 --- /dev/null +++ b/src/matriqueuser.cpp @@ -0,0 +1,6 @@ +#include "matriqueuser.h" + +MatriqueUser::MatriqueUser(QString userId, Connection* connection) + : User(userId, connection) { + connect(this, &User::avatarChanged, this, &MatriqueUser::inheritedAvatarChanged); +} diff --git a/src/matriqueuser.h b/src/matriqueuser.h new file mode 100644 index 0000000..49df02d --- /dev/null +++ b/src/matriqueuser.h @@ -0,0 +1,23 @@ +#ifndef MATRIQUEUSER_H +#define MATRIQUEUSER_H + +#include "user.h" +#include "room.h" + +#include + +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(64); } + + signals: + void inheritedAvatarChanged(User* user, const Room* roomContext); +}; + +#endif // MATRIQUEUSER_H From 647a2cdbf21c2c9373bc2ade8c5426edad5a9ecc Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 11:56:32 +0800 Subject: [PATCH 07/20] New Setting/Account page. --- qml/Setting.qml | 110 ++++++++++++++++++++++++++-------- qml/component/ImageStatus.qml | 79 ------------------------ qml/main.qml | 13 ---- res.qrc | 1 - src/matriqueuser.h | 2 +- 5 files changed, 87 insertions(+), 118 deletions(-) delete mode 100644 qml/component/ImageStatus.qml diff --git a/qml/Setting.qml b/qml/Setting.qml index 84c07ae..5dab213 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -7,31 +7,84 @@ import Matrique.Settings 0.1 import "component" import "form" +import "qrc:/js/util.js" as Util Page { property alias listModel: accountSettingsListView.model + Page { id: accountForm + parent: null padding: 64 - ListView { + ColumnLayout { anchors.fill: parent - id: accountSettingsListView + ListView { + Layout.fillWidth: true + Layout.fillHeight: true - spacing: 0 + id: accountSettingsListView - delegate: RowLayout{ - Label { - text: accountID - } - ItemDelegate { - text: "Logout" - onClicked: matriqueController.logout(connection); + delegate: 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: name + defaultColor: Util.stringToColor(name) + image: avatar + } + + ColumnLayout { + Label { + text: name + } + Label { + text: accountID + } + } + } + + 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) + } } } + + Button { + text: "Add Account" + flat: true + highlighted: true + + onClicked: Util.pushToStack(stackView, loginPage) + } } } @@ -40,6 +93,8 @@ Page { parent: null + padding: 64 + Column { Switch { text: "Lazy load at initial sync" @@ -69,6 +124,8 @@ Page { parent: null + padding: 64 + Column { Switch { text: "Dark theme" @@ -95,6 +152,7 @@ Page { Page { id: aboutForm + parent: null padding: 64 @@ -112,48 +170,52 @@ Page { } } - RowLayout { - ColumnLayout { - Layout.preferredWidth: 240 - Layout.fillHeight: true + Rectangle { + width: 240 + height: parent.height - spacing: 0 + id: settingDrawer + + color: MSettings.darkTheme ? "#323232" : "#f3f3f3" + + Column { + anchors.fill: parent ItemDelegate { - Layout.fillWidth: true + width: parent.width text: "Account" onClicked: pushToStack(accountForm) } ItemDelegate { - Layout.fillWidth: true + width: parent.width text: "General" onClicked: pushToStack(generalForm) } ItemDelegate { - Layout.fillWidth: true + width: parent.width text: "Appearance" onClicked: pushToStack(appearanceForm) } ItemDelegate { - Layout.fillWidth: true + width: parent.width text: "About" onClicked: pushToStack(aboutForm) } } + } - StackView { - Layout.fillWidth: true - Layout.fillHeight: true + StackView { + anchors.fill: parent + anchors.leftMargin: settingDrawer.width - id: settingStackView - } + id: settingStackView } function pushToStack(item) { diff --git a/qml/component/ImageStatus.qml b/qml/component/ImageStatus.qml deleted file mode 100644 index 2144783..0000000 --- a/qml/component/ImageStatus.qml +++ /dev/null @@ -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; - } -} diff --git a/qml/main.qml b/qml/main.qml index b0dd9c1..c2648e9 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -242,19 +242,6 @@ ApplicationWindow { } } - SideNavButton { - Layout.fillWidth: true - Layout.preferredHeight: width - - MaterialIcon { - anchors.fill: parent - - icon: "\ue8b8" - color: "white" - } - page: loginPage - } - SideNavButton { Layout.fillWidth: true Layout.preferredHeight: width diff --git a/res.qrc b/res.qrc index cebc6a2..0095a76 100644 --- a/res.qrc +++ b/res.qrc @@ -6,7 +6,6 @@ asset/font/material.ttf qml/Login.qml qml/main.qml - qml/component/ImageStatus.qml qml/form/RoomForm.qml qml/Room.qml qml/component/SideNavButton.qml diff --git a/src/matriqueuser.h b/src/matriqueuser.h index 49df02d..11de703 100644 --- a/src/matriqueuser.h +++ b/src/matriqueuser.h @@ -17,7 +17,7 @@ class MatriqueUser : public User { QImage getAvatar() { return avatar(64); } signals: - void inheritedAvatarChanged(User* user, const Room* roomContext); + void inheritedAvatarChanged(User* user, const Room* roomContext); // https://bugreports.qt.io/browse/QTBUG-7684 }; #endif // MATRIQUEUSER_H From 20113fb47f54497824b495d6fedce52718bd2201 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 12:56:16 +0800 Subject: [PATCH 08/20] Improve accountlistmodel. --- qml/Setting.qml | 118 +++++++++++++++++++++++++++++---------- qml/main.qml | 4 +- src/accountlistmodel.cpp | 33 ++--------- src/accountlistmodel.h | 10 +--- 4 files changed, 97 insertions(+), 68 deletions(-) diff --git a/qml/Setting.qml b/qml/Setting.qml index 5dab213..ccf18b2 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -28,52 +28,112 @@ Page { id: accountSettingsListView - delegate: SwipeDelegate { - width: accountSettingsListView.width - height: 64 + delegate: Column { + SwipeDelegate { + width: accountSettingsListView.width + height: 64 - clip: true + clip: true - Row { - anchors.fill: parent - anchors.margins: 8 + Row { + anchors.fill: parent + anchors.margins: 8 - spacing: 8 + spacing: 8 - ImageItem { + ImageItem { + width: parent.height + height: parent.height + + hint: user.displayName + defaultColor: Util.stringToColor(user.displayName) + image: user.avatar + } + + ColumnLayout { + Label { + text: user.displayName + } + Label { + text: user.id + } + } + } + + swipe.right: Rectangle { width: parent.height height: parent.height + anchors.right: parent.right - hint: name - defaultColor: Util.stringToColor(name) - image: avatar + color: Material.accent + + MaterialIcon { + anchors.fill: parent + + icon: "\ue879" + color: "white" + } + + SwipeDelegate.onClicked: matriqueController.logout(connection) } - ColumnLayout { - Label { - text: name - } - Label { - text: accountID - } - } + onClicked: accountSettingsListView.currentIndex == index ? accountSettingsListView.currentIndex = -1 : accountSettingsListView.currentIndex = index } - swipe.right: Rectangle { - width: parent.height - height: parent.height - anchors.right: parent.right + Rectangle { + width: parent.width + height: 2 + visible: accountSettingsListView.currentIndex == index color: Material.accent + } - MaterialIcon { - anchors.fill: parent + ColumnLayout { + visible: accountSettingsListView.currentIndex == index + width: parent.width - 32 + anchors.horizontalCenter: parent.horizontalCenter - icon: "\ue879" - color: "white" + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Label { text: "Homeserver:" } + TextField { + Layout.fillWidth: true + + text: connection.homeserver + selectByMouse: true + } } - SwipeDelegate.onClicked: matriqueController.logout(connection) + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Label { text: "Device ID:" } + TextField { + Layout.fillWidth: true + + text: connection.deviceId + selectByMouse: true + } + } + + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Label { text: "Access Token:" } + TextField { + Layout.fillWidth: true + + text: connection.accessToken + selectByMouse: true + } + } } } } diff --git a/qml/main.qml b/qml/main.qml index c2648e9..a80e99c 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -123,8 +123,8 @@ ApplicationWindow { anchors.fill: parent anchors.margins: 12 - hint: name - image: avatar + hint: user.displayName + image: user.avatar defaultColor: Material.accent } diff --git a/src/accountlistmodel.cpp b/src/accountlistmodel.cpp index d9868c1..198ac4f 100644 --- a/src/accountlistmodel.cpp +++ b/src/accountlistmodel.cpp @@ -12,10 +12,7 @@ void AccountListModel::setController(Controller* value) { m_controller = value; - for (auto c : m_controller->connections()) { - connectConnectionSignals(c); - m_connections.append(c); - }; + for (auto c : m_controller->connections()) m_connections.append(c); connect(m_controller, &Controller::connectionAdded, this, [=](Connection* conn) { @@ -23,7 +20,6 @@ void AccountListModel::setController(Controller* value) { } beginInsertRows(QModelIndex(), m_connections.count(), m_connections.count()); - connectConnectionSignals(conn); m_connections.append(conn); endInsertRows(); }); @@ -57,16 +53,8 @@ QVariant AccountListModel::data(const QModelIndex& index, int role) const { return QVariant(); } auto m_connection = m_connections.at(index.row()); - if (role == NameRole) { - return m_connection->user()->displayname(); - } - if (role == AccountIDRole) { - return m_connection->user()->id(); - } - if (role == AvatarRole) { - if (!m_connection->user()->avatarUrl().isEmpty()) - return m_connection->user()->avatar(64); - return QImage(); + if (role == UserRole) { + return QVariant::fromValue(m_connection->user()); } if (role == ConnectionRole) { return QVariant::fromValue(m_connection); @@ -81,22 +69,9 @@ int AccountListModel::rowCount(const QModelIndex& parent) const { return m_connections.count(); } -void AccountListModel::connectConnectionSignals(Connection* conn) { - connect(conn->user(), &User::avatarChanged, this, [=] { - const auto it = std::find(m_connections.begin(), m_connections.end(), conn); - if (it == m_connections.end()) { - return; - } - const auto idx = index(it - m_connections.begin()); - emit dataChanged(idx, idx, {AvatarRole}); - }); -} - QHash AccountListModel::roleNames() const { QHash roles; - roles[NameRole] = "name"; - roles[AccountIDRole] = "accountID"; - roles[AvatarRole] = "avatar"; + roles[UserRole] = "user"; roles[ConnectionRole] = "connection"; return roles; } diff --git a/src/accountlistmodel.h b/src/accountlistmodel.h index e8a8061..bf252e1 100644 --- a/src/accountlistmodel.h +++ b/src/accountlistmodel.h @@ -11,16 +11,11 @@ class AccountListModel : public QAbstractListModel { Q_PROPERTY(Controller* controller READ controller WRITE setController NOTIFY controllerChanged) public: - enum EventRoles { - NameRole = Qt::UserRole + 1, - AccountIDRole, - AvatarRole, - ConnectionRole - }; + enum EventRoles { UserRole = Qt::UserRole + 1, ConnectionRole }; AccountListModel(QObject* parent = nullptr); - QVariant data(const QModelIndex& index, int role = NameRole) const override; + QVariant data(const QModelIndex& index, int role = UserRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QHash roleNames() const override; @@ -32,7 +27,6 @@ class AccountListModel : public QAbstractListModel { Controller* m_controller; QVector m_connections; - void connectConnectionSignals(Connection* conn); signals: void controllerChanged(); }; From 4b9c416b4a4024995cdefd4bc7bc9091691ca820 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 12:59:35 +0800 Subject: [PATCH 09/20] Disable text field. --- qml/Setting.qml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qml/Setting.qml b/qml/Setting.qml index ccf18b2..ad7fb60 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -104,6 +104,7 @@ Page { text: connection.homeserver selectByMouse: true + readOnly: true } } @@ -118,6 +119,7 @@ Page { text: connection.deviceId selectByMouse: true + readOnly: true } } @@ -132,6 +134,7 @@ Page { text: connection.accessToken selectByMouse: true + readOnly: true } } } From f66e62d4990dbd39ac4eb9071535e859a38f92e7 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 15:01:01 +0800 Subject: [PATCH 10/20] Init theming support. --- qml/Room.qml | 1 + qml/Setting.qml | 34 ++++++++++++++++++++++++++-------- qml/component/RoomDrawer.qml | 2 ++ qml/main.qml | 2 ++ src/controller.cpp | 8 ++++++++ src/controller.h | 4 +++- src/matriqueroom.h | 2 +- src/matriqueuser.h | 2 +- src/roomlistmodel.cpp | 2 +- src/userlistmodel.cpp | 2 +- 10 files changed, 46 insertions(+), 13 deletions(-) diff --git a/qml/Room.qml b/qml/Room.qml index 3c16583..b95bc90 100644 --- a/qml/Room.qml +++ b/qml/Room.qml @@ -1,6 +1,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 import Matrique 0.1 import Matrique.Settings 0.1 diff --git a/qml/Setting.qml b/qml/Setting.qml index ad7fb60..95ec054 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -29,6 +29,8 @@ Page { id: accountSettingsListView delegate: Column { + spacing: 16 + SwipeDelegate { width: accountSettingsListView.width height: 64 @@ -80,19 +82,35 @@ Page { onClicked: accountSettingsListView.currentIndex == index ? accountSettingsListView.currentIndex = -1 : accountSettingsListView.currentIndex = index } - Rectangle { - width: parent.width - height: 2 - visible: accountSettingsListView.currentIndex == index - - color: Material.accent - } - ColumnLayout { visible: accountSettingsListView.currentIndex == index width: parent.width - 32 anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + + ListView { + Layout.fillWidth: true + Layout.preferredHeight: 32 + + orientation: ListView.Horizontal + + model: ["#498882", "#2196F3"] + + delegate: Rectangle { + width: parent.height + height: parent.height + + color: modelData + + MouseArea { + anchors.fill: parent + + onClicked: matriqueController.setColor(connection.localUserId, modelData) + } + } + } + RowLayout { Layout.fillWidth: true diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index a776444..cc4668a 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -56,6 +56,7 @@ Drawer { id: roomNameField text: room && room.name ? room.name : "" + selectByMouse: true } ItemDelegate { @@ -77,6 +78,7 @@ Drawer { id: roomTopicField text: room && room.topic ? room.topic : "" + selectByMouse: true } ItemDelegate { diff --git a/qml/main.qml b/qml/main.qml index a80e99c..fc18f03 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -25,6 +25,8 @@ ApplicationWindow { Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light + Material.accent: matriqueController.color(currentConnection ? currentConnection.localUserId : "") + FontLoader { id: materialFont; source: "qrc:/asset/font/material.ttf" } Controller { diff --git a/src/controller.cpp b/src/controller.cpp index 4c03435..4ade983 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -222,3 +222,11 @@ 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()); +} diff --git a/src/controller.h b/src/controller.h index e42bd37..d6297c1 100644 --- a/src/controller.h +++ b/src/controller.h @@ -40,12 +40,14 @@ class Controller : public QObject { } } - QVector m_connections; + Q_INVOKABLE QColor color(QString userId); + Q_INVOKABLE void setColor(QString userId, QColor newColor); private: QClipboard* m_clipboard = QApplication::clipboard(); QSystemTrayIcon* tray = new QSystemTrayIcon(); QMenu* trayMenu = new QMenu(); + QVector m_connections; bool m_busy = false; diff --git a/src/matriqueroom.h b/src/matriqueroom.h index 8250ca5..3bfcebc 100644 --- a/src/matriqueroom.h +++ b/src/matriqueroom.h @@ -19,7 +19,7 @@ class MatriqueRoom : public Room { explicit MatriqueRoom(Connection* connection, QString roomId, JoinState joinState = {}); - QImage getAvatar() { return avatar(64); } + QImage getAvatar() { return avatar(128); } const QString& cachedInput() const { return m_cachedInput; } void setCachedInput(const QString& input) { diff --git a/src/matriqueuser.h b/src/matriqueuser.h index 11de703..7415de8 100644 --- a/src/matriqueuser.h +++ b/src/matriqueuser.h @@ -14,7 +14,7 @@ class MatriqueUser : public User { public: MatriqueUser(QString userId, Connection* connection); - QImage getAvatar() { return avatar(64); } + QImage getAvatar() { return avatar(128); } signals: void inheritedAvatarChanged(User* user, const Room* roomContext); // https://bugreports.qt.io/browse/QTBUG-7684 diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 3ab5b56..af50d3f 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -15,7 +15,7 @@ RoomListModel::~RoomListModel() {} void RoomListModel::setConnection(Connection* connection) { if (connection == m_connection) return; - m_connection->disconnect(this); + if (m_connection) m_connection->disconnect(this); if (!connection) { qDebug() << "Removing current connection..."; m_connection = nullptr; diff --git a/src/userlistmodel.cpp b/src/userlistmodel.cpp index eac3523..334d09c 100644 --- a/src/userlistmodel.cpp +++ b/src/userlistmodel.cpp @@ -69,7 +69,7 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { } if (role == AvatarRole) { if (!user->avatarUrl(m_currentRoom).isEmpty()) - return user->avatar(32, m_currentRoom); + return user->avatar(64, m_currentRoom); return QImage(); } From 17aa5cbf16e550210e1f8d7830f124b62de496a0 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 16:22:45 +0800 Subject: [PATCH 11/20] More UI tweaking. --- matrique.pro | 26 +++++++++++++------------- qml/Setting.qml | 18 +++++++++++++++--- qml/component/SideNavButton.qml | 3 ++- qml/main.qml | 4 ++++ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/matrique.pro b/matrique.pro index 4111400..6b466e2 100644 --- a/matrique.pro +++ b/matrique.pro @@ -69,19 +69,19 @@ mac { ICON = asset/img/icon.icns } -DISTFILES += \ - ChatForm.qml \ - LoginForm.qml \ - main.qml \ - Home.qml \ - Login.qml \ - ImageStatus.qml \ - ButtonDelegate.qml \ - SideNav.qml \ - RoomListForm.qml \ - Room.qml \ - Setting.qml \ - qml/js/md.js \ +#DISTFILES += \ +# ChatForm.qml \ +# LoginForm.qml \ +# main.qml \ +# Home.qml \ +# Login.qml \ +# ImageStatus.qml \ +# ButtonDelegate.qml \ +# SideNav.qml \ +# RoomListForm.qml \ +# Room.qml \ +# Setting.qml \ +# qml/js/md.js \ HEADERS += \ src/controller.h \ diff --git a/qml/Setting.qml b/qml/Setting.qml index 95ec054..017e622 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -28,7 +28,13 @@ Page { id: accountSettingsListView + boundsBehavior: Flickable.DragOverBounds + + clip: true + delegate: Column { + property bool expanded: false + spacing: 16 SwipeDelegate { @@ -79,23 +85,25 @@ Page { SwipeDelegate.onClicked: matriqueController.logout(connection) } - onClicked: accountSettingsListView.currentIndex == index ? accountSettingsListView.currentIndex = -1 : accountSettingsListView.currentIndex = index + onClicked: expanded = !expanded } ColumnLayout { - visible: accountSettingsListView.currentIndex == index width: parent.width - 32 + height: expanded ? implicitHeight : 0 anchors.horizontalCenter: parent.horizontalCenter spacing: 0 + clip: true + ListView { Layout.fillWidth: true Layout.preferredHeight: 32 orientation: ListView.Horizontal - model: ["#498882", "#2196F3"] + model: ["#498882", "#42a5f5", "#5c6bc0", "#7e57c2", "#ab47bc", "#ff7043"] delegate: Rectangle { width: parent.height @@ -155,6 +163,10 @@ Page { readOnly: true } } + + Behavior on height { + PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } + } } } } diff --git a/qml/component/SideNavButton.qml b/qml/component/SideNavButton.qml index 9ec0978..78fab83 100644 --- a/qml/component/SideNavButton.qml +++ b/qml/component/SideNavButton.qml @@ -8,12 +8,13 @@ import "qrc:/js/util.js" as Util ItemDelegate { property var page readonly property bool selected: stackView.currentItem === page + property color highlightColor: Material.accent Rectangle { width: selected ? 4 : 0 height: parent.height - color: Material.accent + color: highlightColor Behavior on width { PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } diff --git a/qml/main.qml b/qml/main.qml index fc18f03..b9b5842 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -117,6 +117,8 @@ ApplicationWindow { spacing: 0 + clip: true + delegate: SideNavButton { width: parent.width height: width @@ -130,6 +132,8 @@ ApplicationWindow { defaultColor: Material.accent } + highlightColor: matriqueController.color(user.id) + page: roomPage onClicked: accountListView.currentConnection = connection From e6beb5f6a820e8a12e4383fd326a22b0260f6d24 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 18:29:41 +0800 Subject: [PATCH 12/20] More UI tweaks. --- qml/Setting.qml | 2 ++ qml/component/RoomDrawer.qml | 7 +++++++ qml/form/RoomListForm.qml | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/qml/Setting.qml b/qml/Setting.qml index 017e622..81787e4 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -172,6 +172,8 @@ Page { } Button { + Layout.fillWidth: true + text: "Add Account" flat: true highlighted: true diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index cc4668a..ee8be48 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -48,6 +48,13 @@ Drawer { 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 { Layout.fillWidth: true diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index d134125..7eb4e4e 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -39,7 +39,7 @@ Item { bottomPadding: 0 placeholderText: "Search..." - background: Rectangle { color: MSettings.darkTheme ? "#282828" : "#fafafa" } + background: Rectangle { color: MSettings.darkTheme ? "#303030" : "#fafafa" } Shortcut { sequence: StandardKey.Find @@ -107,7 +107,7 @@ Item { width: parent.width height: 64 - color: MSettings.darkTheme ? "#282828" : "#fafafa" + color: MSettings.darkTheme ? "#303030" : "#fafafa" AutoMouseArea { anchors.fill: parent From 63c7601942eeef17ee5258015a601cfb04a21759 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Mon, 10 Sep 2018 19:46:04 +0800 Subject: [PATCH 13/20] Fix a bug that avatar does not show after a state event. --- qml/component/MessageDelegate.qml | 2 +- src/messageeventmodel.cpp | 25 +++++++++++++++++-------- src/messageeventmodel.h | 6 +++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index 5d6b26c..fcf6527 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -7,7 +7,7 @@ import Matrique.Settings 0.1 import "qrc:/js/util.js" as Util 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 sentByMe: author === currentRoom.localUser readonly property bool isText: eventType === "notice" || eventType === "message" diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index f512249..9dac890 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -15,6 +15,7 @@ QHash MessageEventModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); roles[EventTypeRole] = "eventType"; + roles[AboveEventTypeRole] = "aboveEventType"; roles[EventIdRole] = "eventId"; roles[TimeRole] = "time"; roles[AboveTimeRole] = "aboveTime"; @@ -77,7 +78,8 @@ void MessageEventModel::setRoom(MatriqueRoom* room) { auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; refreshEventRoles(rowBelowInserted, - {AboveAuthorRole, AboveSectionRole}); + {AboveEventTypeRole, AboveAuthorRole, + AboveSectionRole, AboveTimeRole}); } for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) @@ -107,7 +109,8 @@ void MessageEventModel::setRoom(MatriqueRoom* room) { refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 refreshEventRoles(timelineBaseIndex() - 1, - {AboveAuthorRole, AboveSectionRole}); + {AboveEventTypeRole, AboveAuthorRole, + AboveSectionRole, AboveTimeRole}); }); connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); @@ -621,14 +624,20 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return role == TimeRole ? QVariant(ts) : renderDate(ts); } - if (role == AboveSectionRole || role == AboveAuthorRole || - role == AboveTimeRole) + if (role == AboveEventTypeRole || role == AboveSectionRole || + role == AboveAuthorRole || role == AboveTimeRole) for (auto r = row + 1; r < rowCount(); ++r) { auto i = index(r); - if (data(i, SpecialMarksRole) != EventStatus::Hidden) - return data(i, role == AboveSectionRole - ? SectionRole - : role == AboveAuthorRole ? AuthorRole : TimeRole); + if (data(i, SpecialMarksRole) != EventStatus::Hidden) switch (role) { + case AboveEventTypeRole: + return data(i, EventTypeRole); + case AboveSectionRole: + return data(i, SectionRole); + case AboveAuthorRole: + return data(i, AuthorRole); + case AboveTimeRole: + return data(i, TimeRole); + } } return {}; diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 8161e3e..d672bda 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -1,19 +1,19 @@ #ifndef MESSAGEEVENTMODEL_H #define MESSAGEEVENTMODEL_H -#include "room.h" #include "matriqueroom.h" +#include "room.h" #include class MessageEventModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY( - MatriqueRoom* room READ getRoom WRITE setRoom NOTIFY roomChanged) + Q_PROPERTY(MatriqueRoom* room READ getRoom WRITE setRoom NOTIFY roomChanged) public: enum EventRoles { EventTypeRole = Qt::UserRole + 1, + AboveEventTypeRole, EventIdRole, TimeRole, AboveTimeRole, From 421316aa000d15d3e78d53df0f3ee2f06a77db02 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Tue, 11 Sep 2018 13:14:56 +0800 Subject: [PATCH 14/20] "View source" dialog. --- qml/component/GenericBubble.qml | 10 +++++++++- qml/component/RoomDrawer.qml | 21 +++++++++++++++++++- qml/component/SideNavButton.qml | 2 +- qml/form/RoomForm.qml | 34 +++++++++++++++++++++++++++++++-- qml/form/RoomListForm.qml | 10 ++++++++-- qml/main.qml | 2 ++ qml/menu/MessageContextMenu.qml | 23 ++++++++++++++-------- qml/menu/RoomContextMenu.qml | 17 ++++++++--------- src/messageeventmodel.cpp | 4 ++-- src/userlistmodel.cpp | 4 ++++ src/userlistmodel.h | 2 +- 11 files changed, 102 insertions(+), 27 deletions(-) diff --git a/qml/component/GenericBubble.qml b/qml/component/GenericBubble.qml index 3c754be..917108a 100644 --- a/qml/component/GenericBubble.qml +++ b/qml/component/GenericBubble.qml @@ -15,7 +15,15 @@ Control { AutoMouseArea { anchors.fill: parent - onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/MessageContextMenu.qml").createObject(this) + onSecondaryClicked: { + messageContextMenu.row = messageRow + messageContextMenu.plainText = plainText + messageContextMenu.toolTip = toolTip + messageContextMenu.eventId = eventId + messageContextMenu.eventType = eventType + messageContextMenu.canRedact = sentByMe + messageContextMenu.popup() + } } background: Rectangle { color: colored ? Material.accent : highlighted ? Material.primary : backgroundColor } diff --git a/qml/component/RoomDrawer.qml b/qml/component/RoomDrawer.qml index ee8be48..04db9e4 100644 --- a/qml/component/RoomDrawer.qml +++ b/qml/component/RoomDrawer.qml @@ -110,7 +110,7 @@ Drawer { room: roomDrawer.room } - delegate: ItemDelegate { + delegate: SwipeDelegate { width: parent.width height: 48 @@ -134,6 +134,25 @@ Drawer { 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" + } + + SwipeDelegate.onClicked: room.kickMember(userId) + } + + onClicked: inputField.insert(inputField.cursorPosition, name) } ScrollBar.vertical: ScrollBar {} diff --git a/qml/component/SideNavButton.qml b/qml/component/SideNavButton.qml index 78fab83..d2fb7e3 100644 --- a/qml/component/SideNavButton.qml +++ b/qml/component/SideNavButton.qml @@ -7,7 +7,7 @@ import "qrc:/js/util.js" as Util ItemDelegate { property var page - readonly property bool selected: stackView.currentItem === page + property bool selected: stackView.currentItem === page property color highlightColor: Material.accent Rectangle { diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 57bf62c..9b48cc8 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -1,13 +1,13 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 -import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 import QtGraphicalEffects 1.0 import Matrique 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/util.js" as Util @@ -225,6 +225,36 @@ Item { 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 { diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index 7eb4e4e..f9c9c11 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -8,7 +8,8 @@ import Matrique 0.1 import SortFilterProxyModel 0.2 import Matrique.Settings 0.1 -import "../component" +import "qrc:/qml/component" +import "qrc:/qml/menu" import "qrc:/js/util.js" as Util Item { @@ -114,7 +115,10 @@ Item { hoverEnabled: MSettings.miniMode - onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/RoomContextMenu.qml").createObject(this) + onSecondaryClicked: { + roomContextMenu.room = currentRoom + roomContextMenu.popup() + } onPrimaryClicked: category === RoomType.Invited ? inviteDialog.open() : enteredRoom = currentRoom ToolTip.visible: MSettings.miniMode && containsMouse @@ -198,6 +202,8 @@ Item { horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined } + RoomContextMenu { id: roomContextMenu } + Dialog { id: inviteDialog parent: ApplicationWindow.overlay diff --git a/qml/main.qml b/qml/main.qml index b9b5842..03a2927 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -123,6 +123,8 @@ ApplicationWindow { width: parent.width height: width + selected: stackView.currentItem === page && currentConnection === connection + ImageItem { anchors.fill: parent anchors.margins: 12 diff --git a/qml/menu/MessageContextMenu.qml b/qml/menu/MessageContextMenu.qml index 1fbfa11..a8c93d3 100644 --- a/qml/menu/MessageContextMenu.qml +++ b/qml/menu/MessageContextMenu.qml @@ -2,6 +2,13 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 Menu { + property var row + property bool canRedact + property string eventType + property string plainText + property string toolTip + property string eventId + readonly property bool isFile: eventType === "video" || eventType === "audio" || eventType === "file" || eventType === "image" id: messageContextMenu @@ -12,32 +19,32 @@ Menu { onTriggered: matriqueController.copyToClipboard(plainText) } MenuItem { - text: "Copy Source" + text: "View Source" - onTriggered: matriqueController.copyToClipboard(toolTip) + onTriggered: { + sourceDialog.sourceText = toolTip + sourceDialog.open() + } } MenuItem { visible: isFile height: visible ? undefined : 0 text: "Open Externally" - onTriggered: messageRow.openExternally() + onTriggered: row.openExternally() } MenuItem { visible: isFile height: visible ? undefined : 0 text: "Save As" - onTriggered: messageRow.saveFileAs() + onTriggered: row.saveFileAs() } MenuItem { - visible: sentByMe + visible: canRedact height: visible ? undefined : 0 text: "Redact" onTriggered: currentRoom.redactEvent(eventId) } - - Component.onCompleted: popup() - onClosed: messageContextMenu.destroy() } diff --git a/qml/menu/RoomContextMenu.qml b/qml/menu/RoomContextMenu.qml index 9aff67f..ee81ca2 100644 --- a/qml/menu/RoomContextMenu.qml +++ b/qml/menu/RoomContextMenu.qml @@ -2,34 +2,33 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 Menu { + property var room: null + id: roomListMenu MenuItem { text: "Favourite" checkable: true - checked: currentRoom && currentRoom.isFavourite + checked: room && room.isFavourite - onTriggered: currentRoom.isFavourite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", "1") + onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", "1") } MenuItem { text: "Deprioritize" checkable: true - checked: currentRoom && currentRoom.isLowPriority + checked: room && room.isLowPriority - onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1") + onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", "1") } MenuSeparator {} MenuItem { text: "Mark as Read" - onTriggered: currentRoom.markAllMessagesAsRead() + onTriggered: room.markAllMessagesAsRead() } MenuItem { text: "Leave Room" - onTriggered: currentRoom.forget() + onTriggered: room.forget() } - - Component.onCompleted: popup() - onClosed: roomListMenu.destroy() } diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 9dac890..2c5525a 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -373,7 +373,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { : tr("self-unbanned"); } 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"); case MembershipType::Ban: return (e.senderId() != e.userId()) @@ -472,7 +472,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { : tr("self-unbanned"); } 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"); case MembershipType::Ban: return (e.senderId() != e.userId()) diff --git a/src/userlistmodel.cpp b/src/userlistmodel.cpp index 334d09c..8902d40 100644 --- a/src/userlistmodel.cpp +++ b/src/userlistmodel.cpp @@ -67,6 +67,9 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { if (role == NameRole) { return user->displayname(m_currentRoom); } + if (role == UserIDRole) { + return user->id(); + } if (role == AvatarRole) { if (!user->avatarUrl(m_currentRoom).isEmpty()) return user->avatar(64, m_currentRoom); @@ -126,6 +129,7 @@ int UserListModel::findUserPos(const QString& username) const { QHash UserListModel::roleNames() const { QHash roles; roles[NameRole] = "name"; + roles[UserIDRole] = "userId"; roles[AvatarRole] = "avatar"; return roles; } diff --git a/src/userlistmodel.h b/src/userlistmodel.h index 068cfa6..fd01a5e 100644 --- a/src/userlistmodel.h +++ b/src/userlistmodel.h @@ -17,7 +17,7 @@ class UserListModel : public QAbstractListModel { Q_PROPERTY( QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged) public: - enum EventRoles { NameRole = Qt::UserRole + 1, AvatarRole }; + enum EventRoles { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole }; using User = QMatrixClient::User; From ebe69fd4c02c979d4b15e80be5e016505f8fe8da Mon Sep 17 00:00:00 2001 From: Black Hat Date: Tue, 11 Sep 2018 14:58:07 +0800 Subject: [PATCH 15/20] Fix broken accept/decline invitation. --- qml/form/RoomListForm.qml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index f9c9c11..98f209f 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -119,7 +119,14 @@ Item { roomContextMenu.room = currentRoom roomContextMenu.popup() } - onPrimaryClicked: category === RoomType.Invited ? inviteDialog.open() : enteredRoom = currentRoom + onPrimaryClicked: { + if (category === RoomType.Invited) { + inviteDialog.currentRoom = currentRoom + inviteDialog.open() + } else { + enteredRoom = currentRoom + } + } ToolTip.visible: MSettings.miniMode && containsMouse ToolTip.text: name @@ -205,6 +212,8 @@ Item { RoomContextMenu { id: roomContextMenu } Dialog { + property var currentRoom + id: inviteDialog parent: ApplicationWindow.overlay From 2d2d35fcf502bd57e0f09247097bc12d9d0975f7 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Wed, 12 Sep 2018 08:27:34 +0800 Subject: [PATCH 16/20] Simplify menu code and tweak UI. --- qml/MatriqueSettings.qml | 2 +- qml/Setting.qml | 9 +++++---- qml/component/GenericBubble.qml | 6 +----- qml/form/RoomForm.qml | 2 +- qml/form/RoomListForm.qml | 11 +++++++---- qml/menu/MessageContextMenu.qml | 18 +++++++----------- qml/menu/RoomContextMenu.qml | 15 ++++++++------- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/qml/MatriqueSettings.qml b/qml/MatriqueSettings.qml index 1c8ead1..60d4509 100644 --- a/qml/MatriqueSettings.qml +++ b/qml/MatriqueSettings.qml @@ -4,7 +4,7 @@ import Qt.labs.settings 1.0 Settings { property bool lazyLoad: true - property bool richText + property bool richText: true property bool pressAndHold property bool rearrangeByActivity diff --git a/qml/Setting.qml b/qml/Setting.qml index 81787e4..9a76d99 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -35,7 +35,7 @@ Page { delegate: Column { property bool expanded: false - spacing: 16 + spacing: 8 SwipeDelegate { width: accountSettingsListView.width @@ -99,15 +99,18 @@ Page { ListView { Layout.fillWidth: true - Layout.preferredHeight: 32 + 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 @@ -122,8 +125,6 @@ Page { RowLayout { Layout.fillWidth: true - spacing: 16 - Label { text: "Homeserver:" } TextField { Layout.fillWidth: true diff --git a/qml/component/GenericBubble.qml b/qml/component/GenericBubble.qml index 917108a..5d8e3b9 100644 --- a/qml/component/GenericBubble.qml +++ b/qml/component/GenericBubble.qml @@ -17,11 +17,7 @@ Control { onSecondaryClicked: { messageContextMenu.row = messageRow - messageContextMenu.plainText = plainText - messageContextMenu.toolTip = toolTip - messageContextMenu.eventId = eventId - messageContextMenu.eventType = eventType - messageContextMenu.canRedact = sentByMe + messageContextMenu.model = model messageContextMenu.popup() } } diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 9b48cc8..fdd0094 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -89,7 +89,7 @@ Item { Layout.fillWidth: true Layout.fillHeight: true - text: currentRoom ? currentRoom.topic : "" + text: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : "" color: "white" elide: Text.ElideRight wrapMode: Text.NoWrap diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index 98f209f..58d9c91 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -116,7 +116,7 @@ Item { hoverEnabled: MSettings.miniMode onSecondaryClicked: { - roomContextMenu.room = currentRoom + roomContextMenu.model = model roomContextMenu.popup() } onPrimaryClicked: { @@ -141,11 +141,14 @@ Item { } Rectangle { - width: 4 + width: unreadCount > 0 || highlighted ? 4 : 0 height: parent.height color: Material.accent - visible: unreadCount > 0 || highlighted + + Behavior on width { + PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } + } } RowLayout { @@ -187,7 +190,7 @@ Item { Layout.fillWidth: 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 wrapMode: Text.NoWrap } diff --git a/qml/menu/MessageContextMenu.qml b/qml/menu/MessageContextMenu.qml index a8c93d3..a5354e6 100644 --- a/qml/menu/MessageContextMenu.qml +++ b/qml/menu/MessageContextMenu.qml @@ -2,27 +2,23 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 Menu { - property var row - property bool canRedact - property string eventType - property string plainText - property string toolTip - property string eventId + property var row: null + property var model: null - readonly property bool isFile: eventType === "video" || eventType === "audio" || eventType === "file" || eventType === "image" + readonly property bool isFile: model && (model.eventType === "video" || model.eventType === "audio" || model.eventType === "file" || model.eventType === "image") id: messageContextMenu MenuItem { text: "Copy" - onTriggered: matriqueController.copyToClipboard(plainText) + onTriggered: matriqueController.copyToClipboard(model.plainText) } MenuItem { text: "View Source" onTriggered: { - sourceDialog.sourceText = toolTip + sourceDialog.sourceText = model.toolTip sourceDialog.open() } } @@ -41,10 +37,10 @@ Menu { onTriggered: row.saveFileAs() } MenuItem { - visible: canRedact + visible: model && model.author === currentRoom.localUser height: visible ? undefined : 0 text: "Redact" - onTriggered: currentRoom.redactEvent(eventId) + onTriggered: currentRoom.redactEvent(model.eventId) } } diff --git a/qml/menu/RoomContextMenu.qml b/qml/menu/RoomContextMenu.qml index ee81ca2..451a990 100644 --- a/qml/menu/RoomContextMenu.qml +++ b/qml/menu/RoomContextMenu.qml @@ -1,34 +1,35 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 +import Matrique 0.1 Menu { - property var room: null + property var model: null id: roomListMenu MenuItem { text: "Favourite" checkable: true - checked: room && room.isFavourite + checked: model && model.category === RoomType.Favorite - onTriggered: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", "1") + onTriggered: model.category === RoomType.Favorite ? model.currentRoom.removeTag("m.favourite") : model.currentRoom.addTag("m.favourite", "1") } MenuItem { text: "Deprioritize" checkable: true - checked: room && room.isLowPriority + checked: model && model.category === RoomType.Deprioritized - onTriggered: room.isLowPriority ? room.removeTag("m.lowpriority") : room.addTag("m.lowpriority", "1") + onTriggered: model.category === RoomType.Deprioritized ? model.currentRoom.removeTag("m.lowpriority") : model.currentRoom.addTag("m.lowpriority", "1") } MenuSeparator {} MenuItem { text: "Mark as Read" - onTriggered: room.markAllMessagesAsRead() + onTriggered: model.currentRoom.markAllMessagesAsRead() } MenuItem { text: "Leave Room" - onTriggered: room.forget() + onTriggered: model.currentRoom.forget() } } From 5890a0e13378f8142c79e7a56cb21ade693d0624 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 13 Sep 2018 08:22:41 +0800 Subject: [PATCH 17/20] Somewhat better login logic. --- matrique.pro | 8 +++++++- qml/Login.qml | 2 ++ qml/Setting.qml | 2 +- qml/main.qml | 6 ++++++ src/controller.cpp | 1 + src/controller.h | 5 +++++ 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/matrique.pro b/matrique.pro index 6b466e2..0fabc98 100644 --- a/matrique.pro +++ b/matrique.pro @@ -7,7 +7,13 @@ CONFIG += object_parallel_to_source TARGET = matrique -include(include/libqmatrixclient/libqmatrixclient.pri) +packagesExist(QMatrixClient) { + message("Found libQMatrixClient via pkg-config.") + CONFIG += link_pkgconfig + PKGCONFIG += QMatrixClient +} else { + include(include/libqmatrixclient/libqmatrixclient.pri) +} include(include/SortFilterProxyModel/SortFilterProxyModel.pri) # The following define makes your compiler emit warnings if you use diff --git a/qml/Login.qml b/qml/Login.qml index 6ba5c19..1fb0e10 100644 --- a/qml/Login.qml +++ b/qml/Login.qml @@ -152,6 +152,8 @@ Page { } controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text) + + controller.connectionAdded.connect(function() { stackView.pop() }) } } } diff --git a/qml/Setting.qml b/qml/Setting.qml index 9a76d99..6774ffb 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -179,7 +179,7 @@ Page { flat: true highlighted: true - onClicked: Util.pushToStack(stackView, loginPage) + onClicked: stackView.push(loginPage) } } } diff --git a/qml/main.qml b/qml/main.qml index 03a2927..5f3de31 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -294,4 +294,10 @@ ApplicationWindow { property: "connection" value: currentConnection } + + Component.onCompleted: { + matriqueController.initiated.connect(function() { + if (matriqueController.accountCount == 0) stackView.push(loginPage) + }) + } } diff --git a/src/controller.cpp b/src/controller.cpp index 4ade983..52c7f2e 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -141,6 +141,7 @@ void Controller::invokeLogin() { c->connectWithToken(account.userId(), accessToken, account.deviceId()); } } + emit initiated(); } QByteArray Controller::loadAccessToken(const AccountSettings& account) { diff --git a/src/controller.h b/src/controller.h index d6297c1..8f30954 100644 --- a/src/controller.h +++ b/src/controller.h @@ -17,6 +17,8 @@ class Controller : public QObject { Q_OBJECT Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged) + Q_PROPERTY(int accountCount READ accountCount NOTIFY connectionAdded NOTIFY + connectionDropped) public: explicit Controller(QObject* parent = nullptr); @@ -40,6 +42,8 @@ class Controller : public QObject { } } + int accountCount() { return m_connections.count(); } + Q_INVOKABLE QColor color(QString userId); Q_INVOKABLE void setColor(QString userId, QColor newColor); @@ -66,6 +70,7 @@ class Controller : public QObject { void toggleWindow(); void connectionAdded(Connection* conn); void connectionDropped(Connection* conn); + void initiated(); public slots: void logout(Connection* conn); From 1169a0c4d18f85e391fbad821dfd27f01314c0b2 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 13 Sep 2018 08:31:18 +0800 Subject: [PATCH 18/20] Update libqmatrixclient. --- include/libqmatrixclient | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/libqmatrixclient b/include/libqmatrixclient index 3dfcd0f..875514e 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit 3dfcd0f4f4501dba5925d894b7f0fbc9414549c8 +Subproject commit 875514ee865b00be67542849f94d2c2561ba4137 From 3ef1744b5cf7bf18bf182c4bea84610a23d9cb60 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 13 Sep 2018 11:58:02 +0800 Subject: [PATCH 19/20] UI logic tweaking. --- js/util.js | 4 ++-- qml/main.qml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/js/util.js b/js/util.js index 56d75da..fcd0e97 100644 --- a/js/util.js +++ b/js/util.js @@ -18,8 +18,8 @@ function pushToStack(stack, page) { if(stack.depth === 1) { stack.replace(page) } else { - stack.clear() - stack.push(page) + stack.pop(null) + stack.replace(page) } } } diff --git a/qml/main.qml b/qml/main.qml index 5f3de31..6e46b2d 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -9,6 +9,7 @@ import Matrique.Settings 0.1 import "component" import "form" +import "qrc:/js/util.js" as Util ApplicationWindow { readonly property var currentConnection: accountListView.currentConnection ? accountListView.currentConnection : null @@ -131,7 +132,7 @@ ApplicationWindow { hint: user.displayName image: user.avatar - defaultColor: Material.accent + defaultColor: Util.stringToColor(user.displayName) } highlightColor: matriqueController.color(user.id) From 29624c5f59649e53fb0f325360382cb34d80da44 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Thu, 13 Sep 2018 13:05:51 +0800 Subject: [PATCH 20/20] Add usermarker. --- qml/component/MaterialIcon.qml | 21 +++++++-------------- qml/component/MessageDelegate.qml | 28 ++++++++++++++++++++++------ src/messageeventmodel.cpp | 15 +++++++++++++++ src/messageeventmodel.h | 3 ++- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/qml/component/MaterialIcon.qml b/qml/component/MaterialIcon.qml index d1b2f0e..87c3315 100644 --- a/qml/component/MaterialIcon.qml +++ b/qml/component/MaterialIcon.qml @@ -3,20 +3,13 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import Matrique.Settings 0.1 -Item { - property alias icon: iconText.text - property var color: MSettings.darkTheme ? "white" : "black" +Text { + property alias icon: materialLabel.text - id: item + id: materialLabel - Text { - anchors.fill: parent - - id: iconText - font.pointSize: 16 - font.family: materialFont.name - color: item.color - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } + font.pointSize: 16 + font.family: materialFont.name + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter } diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index fcf6527..9e91313 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -93,13 +93,29 @@ RowLayout { active: eventType === "image" || eventType === "file" || eventType === "audio" } - AutoLabel { + Row { Layout.alignment: Qt.AlignRight - visible: Math.abs(time - aboveTime) > 600000 || index == 0 - text: Qt.formatTime(time, "hh:mm") - coloredBackground: highlighted - Material.foreground: "grey" - font.pointSize: 8 + + spacing: 8 + + AutoLabel { + id: timeLabel + + visible: Math.abs(time - aboveTime) > 600000 || index == 0 + text: Qt.formatTime(time, "hh:mm") + coloredBackground: highlighted + Material.foreground: "grey" + font.pointSize: 8 + } + + MaterialIcon { + height: timeLabel.height + + visible: userMarker.length > 0 + icon: "\ue5ca" + color: highlighted ? "white": Material.foreground + font.pointSize: 12 + } } } diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 2c5525a..51fefbb 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -32,6 +32,7 @@ QHash MessageEventModel::roleNames() const { roles[AnnotationRole] = "annotation"; roles[EventResolvedTypeRole] = "eventResolvedType"; roles[PlainTextRole] = "plainText"; + roles[UserMarkerRole] = "userMarker"; return roles; } @@ -137,6 +138,11 @@ void MessageEventModel::setRoom(MatriqueRoom* room) { &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::fileTransferCancelled, this, &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" << room->localUser()->id(); } else @@ -624,6 +630,15 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return role == TimeRole ? QVariant(ts) : renderDate(ts); } + if (role == UserMarkerRole) { + 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) { diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index d672bda..a19cb0a 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -29,6 +29,7 @@ class MessageEventModel : public QAbstractListModel { LongOperationRole, AnnotationRole, PlainTextRole, + UserMarkerRole, // For debugging EventResolvedTypeRole, }; @@ -42,7 +43,7 @@ class MessageEventModel : public QAbstractListModel { int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QHash roleNames() const; + QHash roleNames() const override; private slots: int refreshEvent(const QString& eventId);