Improve public room list and add search functionalities.

This commit is contained in:
Black Hat
2019-12-25 15:38:26 +08:00
parent e5032c686a
commit 273e993bdb
5 changed files with 263 additions and 55 deletions

View File

@@ -143,6 +143,7 @@ set(spectral_SRCS
src/spectraluser.h src/spectraluser.h
src/trayicon.h src/trayicon.h
src/userlistmodel.h src/userlistmodel.h
src/publicroomlistmodel.h
src/utils.h src/utils.h
src/accountlistmodel.cpp src/accountlistmodel.cpp
src/controller.cpp src/controller.cpp

View File

@@ -111,7 +111,10 @@ Dialog {
RippleEffect { RippleEffect {
anchors.fill: parent 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()
}
} }
} }

View File

@@ -3,6 +3,7 @@ import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import Spectral.Component 2.0 import Spectral.Component 2.0
import Spectral.Effect 2.0
import Spectral.Setting 0.1 import Spectral.Setting 0.1
import Spectral 0.1 import Spectral 0.1
@@ -11,8 +12,11 @@ Dialog {
property var controller property var controller
property var connection property var connection
property string keyword
property string server
anchors.centerIn: parent anchors.centerIn: parent
width: 360 width: 480
height: window.height - 100 height: window.height - 100
id: root id: root
@@ -20,12 +24,46 @@ Dialog {
title: "Start a Chat" title: "Start a Chat"
contentItem: ColumnLayout { contentItem: ColumnLayout {
AutoTextField { spacing: 0
RowLayout {
Layout.fillWidth: true 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 { MenuSeparator {
@@ -38,55 +76,96 @@ Dialog {
id: publicRoomsListView id: publicRoomsListView
spacing: 8 clip: true
spacing: 4
model: PublicRoomListModel { model: PublicRoomListModel {
id: publicRoomListModel
connection: root.connection connection: root.connection
server: root.server
keyword: root.keyword
} }
delegate: ColumnLayout { delegate: Item {
width: publicRoomsListView.width width: publicRoomsListView.width
height: 40
Label { RowLayout {
Layout.fillWidth: true anchors.fill: parent
anchors.margins: 4
text: name ? name : "No name" spacing: 8
color: MPalette.foreground
font.pixelSize: 13 Avatar {
textFormat: Text.PlainText Layout.preferredWidth: height
elide: Text.ElideRight Layout.fillHeight: true
wrapMode: Text.NoWrap
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 { RippleEffect {
Layout.fillWidth: true anchors.fill: parent
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
} }
} }
ScrollBar.vertical: ScrollBar {} 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: { // onAccepted: {
// var identifier = identifierField.text // var identifier = identifierField.text
// var firstChar = identifier.charAt(0) // var firstChar = identifier.charAt(0)
// if (firstChar == "@") { // if (firstChar == "@") {
// spectralController.createDirectChat(spectralController.connection, identifier) // spectralController.createDirectChat(spectralController.connection, identifier)
// } else if (firstChar == "!" || firstChar == "#") { // } else if (firstChar == "!" || firstChar == "#") {
// spectralController.joinRoom(spectralController.connection, identifier) // spectralController.joinRoom(spectralController.connection, identifier)
// } // }
// } // }
onClosed: destroy() onClosed: destroy()
} }

View File

@@ -1,7 +1,5 @@
#include "publicroomlistmodel.h" #include "publicroomlistmodel.h"
#include "csapi/list_public_rooms.h"
PublicRoomListModel::PublicRoomListModel(QObject* parent) PublicRoomListModel::PublicRoomListModel(QObject* parent)
: QAbstractListModel(parent) {} : QAbstractListModel(parent) {}
@@ -14,6 +12,7 @@ void PublicRoomListModel::setConnection(Connection* conn) {
nextBatch = ""; nextBatch = "";
attempted = false; attempted = false;
rooms.clear(); rooms.clear();
m_server.clear();
if (m_connection) { if (m_connection) {
m_connection->disconnect(this); m_connection->disconnect(this);
@@ -23,33 +22,109 @@ void PublicRoomListModel::setConnection(Connection* conn) {
m_connection = conn; m_connection = conn;
if (job) {
job->abandon();
job = nullptr;
}
if (m_connection) { if (m_connection) {
next(); next();
} }
emit connectionChanged(); 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) { void PublicRoomListModel::next(int count) {
if (count < 1) if (count < 1)
return; return;
if (attempted && nextBatch.isEmpty()) if (job) {
qDebug() << "PublicRoomListModel: Other jobs running, ignore";
return;
}
if (!hasMore())
return; return;
auto job = m_connection->callApi<GetPublicRoomsJob>(count, nextBatch); job = m_connection->callApi<QueryPublicRoomsJob>(
m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword});
connect(job, &BaseJob::success, this, [=] { connect(job, &BaseJob::finished, this, [=] {
auto resp = job->data(); attempted = true;
nextBatch = resp.nextBatch;
this->beginInsertRows({}, rooms.count(), if (job->status() == BaseJob::Success) {
rooms.count() + resp.chunk.count()); auto resp = job->data();
rooms.append(resp.chunk); nextBatch = resp.nextBatch;
this->endInsertRows();
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 { 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()); auto room = rooms.at(index.row());
if (role == NameRole) { 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) { if (role == TopicRole) {
return room.topic; return room.topic;
@@ -76,6 +175,7 @@ QHash<int, QByteArray> PublicRoomListModel::roleNames() const {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[NameRole] = "name";
roles[AvatarRole] = "avatar";
roles[TopicRole] = "topic"; roles[TopicRole] = "topic";
return roles; return roles;
@@ -87,3 +187,7 @@ int PublicRoomListModel::rowCount(const QModelIndex& parent) const {
return rooms.count(); return rooms.count();
} }
bool PublicRoomListModel::hasMore() const {
return !(attempted && nextBatch.isEmpty());
}

View File

@@ -1,20 +1,26 @@
#ifndef PUBLICROOMLISTMODEL_H #ifndef PUBLICROOMLISTMODEL_H
#define PUBLICROOMLISTMODEL_H #define PUBLICROOMLISTMODEL_H
#include <QObject>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QObject>
#include "connection.h" #include "connection.h"
#include "csapi/definitions/public_rooms_response.h" #include "csapi/definitions/public_rooms_response.h"
#include "csapi/list_public_rooms.h"
using namespace Quotient; using namespace Quotient;
class PublicRoomListModel : public QAbstractListModel { class PublicRoomListModel : public QAbstractListModel {
Q_OBJECT 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: public:
enum EventRoles { NameRole = Qt::DisplayRole + 1, TopicRole }; enum EventRoles { NameRole = Qt::DisplayRole + 1, AvatarRole, TopicRole };
PublicRoomListModel(QObject* parent = nullptr); PublicRoomListModel(QObject* parent = nullptr);
@@ -26,18 +32,33 @@ class PublicRoomListModel : public QAbstractListModel {
Connection* connection() const { return m_connection; } Connection* connection() const { return m_connection; }
void setConnection(Connection* value); 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); Q_INVOKABLE void next(int count = 50);
private: private:
Connection* m_connection = nullptr; Connection* m_connection = nullptr;
QString m_server;
QString m_keyword;
bool attempted = false; bool attempted = false;
QString nextBatch; QString nextBatch;
QVector<PublicRoomsChunk> rooms; QVector<PublicRoomsChunk> rooms;
signals: QueryPublicRoomsJob* job = nullptr;
signals:
void connectionChanged(); void connectionChanged();
void serverChanged();
void keywordChanged();
void hasMoreChanged();
}; };
#endif // PUBLICROOMLISTMODEL_H #endif // PUBLICROOMLISTMODEL_H