From 273e993bdb08f6ca6cfdc56f7405c803a2cc21e9 Mon Sep 17 00:00:00 2001 From: Black Hat Date: Wed, 25 Dec 2019 15:38:26 +0800 Subject: [PATCH] Improve public room list and add search functionalities. --- CMakeLists.txt | 1 + .../Spectral/Dialog/AccountDetailDialog.qml | 5 +- imports/Spectral/Dialog/JoinRoomDialog.qml | 149 ++++++++++++++---- src/publicroomlistmodel.cpp | 132 ++++++++++++++-- src/publicroomlistmodel.h | 31 +++- 5 files changed, 263 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51810bf10..3722787cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,7 @@ set(spectral_SRCS src/spectraluser.h src/trayicon.h src/userlistmodel.h + src/publicroomlistmodel.h src/utils.h src/accountlistmodel.cpp src/controller.cpp diff --git a/imports/Spectral/Dialog/AccountDetailDialog.qml b/imports/Spectral/Dialog/AccountDetailDialog.qml index ca37a7ff2..3ec590159 100644 --- a/imports/Spectral/Dialog/AccountDetailDialog.qml +++ b/imports/Spectral/Dialog/AccountDetailDialog.qml @@ -111,7 +111,10 @@ Dialog { RippleEffect { anchors.fill: parent - onPrimaryClicked: joinRoomDialog.createObject(ApplicationWindow.overlay, {"controller": spectralController, "connection": spectralController.connection}).open() + onPrimaryClicked: { + joinRoomDialog.createObject(ApplicationWindow.overlay, {"controller": spectralController, "connection": spectralController.connection}).open() + root.destroy() + } } } diff --git a/imports/Spectral/Dialog/JoinRoomDialog.qml b/imports/Spectral/Dialog/JoinRoomDialog.qml index 791db0303..52060d8cb 100644 --- a/imports/Spectral/Dialog/JoinRoomDialog.qml +++ b/imports/Spectral/Dialog/JoinRoomDialog.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import Spectral.Component 2.0 +import Spectral.Effect 2.0 import Spectral.Setting 0.1 import Spectral 0.1 @@ -11,8 +12,11 @@ Dialog { property var controller property var connection + property string keyword + property string server + anchors.centerIn: parent - width: 360 + width: 480 height: window.height - 100 id: root @@ -20,12 +24,46 @@ Dialog { title: "Start a Chat" contentItem: ColumnLayout { - AutoTextField { + spacing: 0 + + RowLayout { Layout.fillWidth: true - id: identifierField + AutoTextField { + Layout.fillWidth: true - placeholderText: "Room Alias/User ID" + id: identifierField + + placeholderText: "Room Alias/User ID" + + Keys.onReturnPressed: { + keyword = text + } + } + + ComboBox { + Layout.maximumWidth: 120 + + id: serverField + + editable: currentIndex == 1 + + model: ["Local", "Global", "matrix.org"] + + onCurrentIndexChanged: { + if (currentIndex == 0) { + server = "" + } else if (currentIndex == 2) { + server = "matrix.org" + } + } + + Keys.onReturnPressed: { + if (currentIndex == 1) { + server = editText + } + } + } } MenuSeparator { @@ -38,55 +76,96 @@ Dialog { id: publicRoomsListView - spacing: 8 + clip: true + + spacing: 4 model: PublicRoomListModel { + id: publicRoomListModel + connection: root.connection + server: root.server + keyword: root.keyword } - delegate: ColumnLayout { + delegate: Item { width: publicRoomsListView.width + height: 40 - Label { - Layout.fillWidth: true + RowLayout { + anchors.fill: parent + anchors.margins: 4 - text: name ? name : "No name" - color: MPalette.foreground - font.pixelSize: 13 - textFormat: Text.PlainText - elide: Text.ElideRight - wrapMode: Text.NoWrap + spacing: 8 + + Avatar { + Layout.preferredWidth: height + Layout.fillHeight: true + + source: avatar + hint: name + } + + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter + + spacing: 0 + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + text: name + color: MPalette.foreground + font.pixelSize: 13 + textFormat: Text.PlainText + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + + Label { + Layout.fillWidth: true + Layout.fillHeight: true + + visible: text + + text: topic ? topic.replace(/(\r\n\t|\n|\r\t)/gm," ") : "" + color: MPalette.lighter + font.pixelSize: 10 + textFormat: Text.PlainText + elide: Text.ElideRight + wrapMode: Text.NoWrap + } + } } - Label { - Layout.fillWidth: true - - visible: text - - text: topic ? topic.replace(/(\r\n\t|\n|\r\t)/gm," ") : "" - color: MPalette.lighter - font.pixelSize: 10 - textFormat: Text.PlainText - elide: Text.ElideRight - wrapMode: Text.NoWrap + RippleEffect { + anchors.fill: parent } } ScrollBar.vertical: ScrollBar {} + + onContentYChanged: { + if(publicRoomListModel.hasMore && contentHeight - contentY < publicRoomsListView.height + 200) + publicRoomListModel.next(); + } } } -// standardButtons: Dialog.Ok | Dialog.Cancel + // standardButtons: Dialog.Ok | Dialog.Cancel -// onAccepted: { -// var identifier = identifierField.text -// var firstChar = identifier.charAt(0) -// if (firstChar == "@") { -// spectralController.createDirectChat(spectralController.connection, identifier) -// } else if (firstChar == "!" || firstChar == "#") { -// spectralController.joinRoom(spectralController.connection, identifier) -// } -// } + // onAccepted: { + // var identifier = identifierField.text + // var firstChar = identifier.charAt(0) + // if (firstChar == "@") { + // spectralController.createDirectChat(spectralController.connection, identifier) + // } else if (firstChar == "!" || firstChar == "#") { + // spectralController.joinRoom(spectralController.connection, identifier) + // } + // } onClosed: destroy() } diff --git a/src/publicroomlistmodel.cpp b/src/publicroomlistmodel.cpp index 621386209..a7e1e0943 100644 --- a/src/publicroomlistmodel.cpp +++ b/src/publicroomlistmodel.cpp @@ -1,7 +1,5 @@ #include "publicroomlistmodel.h" -#include "csapi/list_public_rooms.h" - PublicRoomListModel::PublicRoomListModel(QObject* parent) : QAbstractListModel(parent) {} @@ -14,6 +12,7 @@ void PublicRoomListModel::setConnection(Connection* conn) { nextBatch = ""; attempted = false; rooms.clear(); + m_server.clear(); if (m_connection) { m_connection->disconnect(this); @@ -23,33 +22,109 @@ void PublicRoomListModel::setConnection(Connection* conn) { m_connection = conn; + if (job) { + job->abandon(); + job = nullptr; + } + if (m_connection) { next(); } emit connectionChanged(); + emit serverChanged(); + emit hasMoreChanged(); +} + +void PublicRoomListModel::setServer(const QString& value) { + if (m_server == value) + return; + + m_server = value; + + beginResetModel(); + + nextBatch = ""; + attempted = false; + rooms.clear(); + + endResetModel(); + + if (job) { + job->abandon(); + job = nullptr; + } + + if (m_connection) { + next(); + } + + emit serverChanged(); + emit hasMoreChanged(); +} + +void PublicRoomListModel::setKeyword(const QString& value) { + if (m_keyword == value) + return; + + m_keyword = value; + + beginResetModel(); + + nextBatch = ""; + attempted = false; + rooms.clear(); + + endResetModel(); + + if (job) { + job->abandon(); + job = nullptr; + } + + if (m_connection) { + next(); + } + + emit keywordChanged(); + emit hasMoreChanged(); } void PublicRoomListModel::next(int count) { if (count < 1) return; - if (attempted && nextBatch.isEmpty()) + if (job) { + qDebug() << "PublicRoomListModel: Other jobs running, ignore"; + + return; + } + + if (!hasMore()) return; - auto job = m_connection->callApi(count, nextBatch); + job = m_connection->callApi( + m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword}); - connect(job, &BaseJob::success, this, [=] { - auto resp = job->data(); - nextBatch = resp.nextBatch; + connect(job, &BaseJob::finished, this, [=] { + attempted = true; - this->beginInsertRows({}, rooms.count(), - rooms.count() + resp.chunk.count()); - rooms.append(resp.chunk); - this->endInsertRows(); + if (job->status() == BaseJob::Success) { + auto resp = job->data(); + nextBatch = resp.nextBatch; + + this->beginInsertRows({}, rooms.count(), + rooms.count() + resp.chunk.count() - 1); + rooms.append(resp.chunk); + this->endInsertRows(); + + if (resp.nextBatch.isEmpty()) { + emit hasMoreChanged(); + } + } + + this->job = nullptr; }); - - connect(job, &BaseJob::finished, this, [=] { attempted = true; }); } QVariant PublicRoomListModel::data(const QModelIndex& index, int role) const { @@ -63,7 +138,31 @@ QVariant PublicRoomListModel::data(const QModelIndex& index, int role) const { } auto room = rooms.at(index.row()); if (role == NameRole) { - return room.name; + auto displayName = room.name; + if (!displayName.isEmpty()) { + return displayName; + } + + displayName = room.canonicalAlias; + if (!displayName.isEmpty()) { + return displayName; + } + + displayName = room.aliases.front(); + if (!displayName.isEmpty()) { + return displayName; + } + + return room.roomId; + } + if (role == AvatarRole) { + auto avatarUrl = room.avatarUrl; + + if (avatarUrl.isEmpty()) { + return ""; + } + + return avatarUrl.remove(0, 6); } if (role == TopicRole) { return room.topic; @@ -76,6 +175,7 @@ QHash PublicRoomListModel::roleNames() const { QHash roles; roles[NameRole] = "name"; + roles[AvatarRole] = "avatar"; roles[TopicRole] = "topic"; return roles; @@ -87,3 +187,7 @@ int PublicRoomListModel::rowCount(const QModelIndex& parent) const { return rooms.count(); } + +bool PublicRoomListModel::hasMore() const { + return !(attempted && nextBatch.isEmpty()); +} diff --git a/src/publicroomlistmodel.h b/src/publicroomlistmodel.h index b0cebe06a..1fa7a5240 100644 --- a/src/publicroomlistmodel.h +++ b/src/publicroomlistmodel.h @@ -1,20 +1,26 @@ #ifndef PUBLICROOMLISTMODEL_H #define PUBLICROOMLISTMODEL_H -#include #include +#include #include "connection.h" #include "csapi/definitions/public_rooms_response.h" +#include "csapi/list_public_rooms.h" using namespace Quotient; class PublicRoomListModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY connectionChanged) + Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY + connectionChanged) + Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged) + Q_PROPERTY( + QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged) + Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged) public: - enum EventRoles { NameRole = Qt::DisplayRole + 1, TopicRole }; + enum EventRoles { NameRole = Qt::DisplayRole + 1, AvatarRole, TopicRole }; PublicRoomListModel(QObject* parent = nullptr); @@ -26,18 +32,33 @@ class PublicRoomListModel : public QAbstractListModel { Connection* connection() const { return m_connection; } void setConnection(Connection* value); + QString server() const { return m_server; } + void setServer(const QString& value); + + QString keyword() const { return m_keyword; } + void setKeyword(const QString& value); + + bool hasMore() const; + Q_INVOKABLE void next(int count = 50); private: Connection* m_connection = nullptr; + QString m_server; + QString m_keyword; bool attempted = false; QString nextBatch; QVector rooms; -signals: + QueryPublicRoomsJob* job = nullptr; + + signals: void connectionChanged(); + void serverChanged(); + void keywordChanged(); + void hasMoreChanged(); }; -#endif // PUBLICROOMLISTMODEL_H +#endif // PUBLICROOMLISTMODEL_H