mirror of https://github.com/Nheko-Reborn/nheko
commit
b01496f9b4
@ -0,0 +1,204 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors |
||||
// |
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
||||
import "./ui" |
||||
import QtQuick 2.9 |
||||
import QtQuick.Controls 2.3 |
||||
import QtQuick.Layouts 1.3 |
||||
import im.nheko 1.0 |
||||
|
||||
ApplicationWindow { |
||||
id: roomDirectoryWindow |
||||
|
||||
property RoomDirectoryModel publicRooms |
||||
|
||||
visible: true |
||||
minimumWidth: 650 |
||||
minimumHeight: 420 |
||||
palette: Nheko.colors |
||||
color: Nheko.colors.window |
||||
modality: Qt.WindowModal |
||||
flags: Qt.Dialog | Qt.WindowCloseButtonHint |
||||
Component.onCompleted: Nheko.reparent(roomDirectoryWindow) |
||||
title: qsTr("Explore Public Rooms") |
||||
|
||||
Shortcut { |
||||
sequence: StandardKey.Cancel |
||||
onActivated: roomDirectoryWindow.close() |
||||
} |
||||
|
||||
ListView { |
||||
id: roomDirView |
||||
|
||||
anchors.fill: parent |
||||
model: publicRooms |
||||
|
||||
ScrollHelper { |
||||
flickable: parent |
||||
anchors.fill: parent |
||||
enabled: !Settings.mobileMode |
||||
} |
||||
|
||||
delegate: Rectangle { |
||||
id: roomDirDelegate |
||||
|
||||
property color background: Nheko.colors.window |
||||
property color importantText: Nheko.colors.text |
||||
property color unimportantText: Nheko.colors.buttonText |
||||
property int avatarSize: fontMetrics.lineSpacing * 4 |
||||
|
||||
color: background |
||||
height: avatarSize + Nheko.paddingLarge |
||||
width: ListView.view.width |
||||
|
||||
RowLayout { |
||||
spacing: Nheko.paddingMedium |
||||
anchors.fill: parent |
||||
anchors.margins: Nheko.paddingLarge |
||||
implicitHeight: textContent.height |
||||
|
||||
Avatar { |
||||
id: roomAvatar |
||||
|
||||
Layout.alignment: Qt.AlignVCenter |
||||
width: avatarSize |
||||
height: avatarSize |
||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/") |
||||
displayName: model.name |
||||
} |
||||
|
||||
ColumnLayout { |
||||
id: textContent |
||||
|
||||
Layout.alignment: Qt.AlignLeft |
||||
width: parent.width - avatar.width |
||||
Layout.preferredWidth: parent.width - avatar.width |
||||
spacing: Nheko.paddingSmall |
||||
|
||||
ElidedLabel { |
||||
Layout.alignment: Qt.AlignBottom |
||||
color: roomDirDelegate.importantText |
||||
elideWidth: textContent.width - numMembersRectangle.width - buttonRectangle.width |
||||
font.pixelSize: fontMetrics.font.pixelSize * 1.1 |
||||
fullText: model.name |
||||
} |
||||
|
||||
RowLayout { |
||||
id: roomDescriptionRow |
||||
|
||||
Layout.preferredWidth: parent.width |
||||
spacing: Nheko.paddingSmall |
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft |
||||
Layout.preferredHeight: fontMetrics.lineSpacing * 4 |
||||
|
||||
Label { |
||||
id: roomTopic |
||||
|
||||
color: roomDirDelegate.unimportantText |
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft |
||||
font.pixelSize: fontMetrics.font.pixelSize |
||||
elide: Text.ElideRight |
||||
maximumLineCount: 2 |
||||
Layout.fillWidth: true |
||||
text: model.topic |
||||
verticalAlignment: Text.AlignVCenter |
||||
wrapMode: Text.WordWrap |
||||
} |
||||
|
||||
Item { |
||||
id: numMembersRectangle |
||||
|
||||
Layout.margins: Nheko.paddingSmall |
||||
width: roomCount.width |
||||
|
||||
Label { |
||||
id: roomCount |
||||
|
||||
color: roomDirDelegate.unimportantText |
||||
anchors.centerIn: parent |
||||
font.pixelSize: fontMetrics.font.pixelSize |
||||
text: model.numMembers.toString() |
||||
} |
||||
|
||||
} |
||||
|
||||
Item { |
||||
id: buttonRectangle |
||||
|
||||
Layout.margins: Nheko.paddingSmall |
||||
width: joinRoomButton.width |
||||
|
||||
Button { |
||||
id: joinRoomButton |
||||
|
||||
visible: publicRooms.canJoinRoom(model.roomid) |
||||
anchors.centerIn: parent |
||||
width: Math.ceil(0.1 * roomDirectoryWindow.width) |
||||
text: "Join" |
||||
onClicked: publicRooms.joinRoom(model.index) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
footer: Item { |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
width: parent.width |
||||
visible: !publicRooms.reachedEndOfPagination && publicRooms.loadingMoreRooms |
||||
// hacky but works |
||||
height: loadingSpinner.height + 2 * Nheko.paddingLarge |
||||
anchors.margins: Nheko.paddingLarge |
||||
|
||||
Spinner { |
||||
id: loadingSpinner |
||||
|
||||
anchors.centerIn: parent |
||||
anchors.margins: Nheko.paddingLarge |
||||
running: visible |
||||
foreground: Nheko.colors.mid |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
publicRooms: RoomDirectoryModel { |
||||
} |
||||
|
||||
header: RowLayout { |
||||
id: searchBarLayout |
||||
|
||||
spacing: Nheko.paddingMedium |
||||
width: parent.width |
||||
implicitHeight: roomSearch.height |
||||
|
||||
MatrixTextField { |
||||
id: roomSearch |
||||
|
||||
Layout.fillWidth: true |
||||
selectByMouse: true |
||||
font.pixelSize: fontMetrics.font.pixelSize |
||||
padding: Nheko.paddingMedium |
||||
color: Nheko.colors.text |
||||
placeholderText: qsTr("Search for public rooms") |
||||
onTextChanged: searchTimer.restart() |
||||
} |
||||
|
||||
Timer { |
||||
id: searchTimer |
||||
|
||||
interval: 350 |
||||
onTriggered: roomDirView.model.setSearchTerm(roomSearch.text) |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,194 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "RoomDirectoryModel.h" |
||||
#include "Cache.h" |
||||
#include "ChatPage.h" |
||||
|
||||
#include <algorithm> |
||||
|
||||
RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &s) |
||||
: QAbstractListModel(parent) |
||||
, server_(s) |
||||
{ |
||||
connect(this, |
||||
&RoomDirectoryModel::fetchedRoomsBatch, |
||||
this, |
||||
&RoomDirectoryModel::displayRooms, |
||||
Qt::QueuedConnection); |
||||
} |
||||
|
||||
QHash<int, QByteArray> |
||||
RoomDirectoryModel::roleNames() const |
||||
{ |
||||
return { |
||||
{Roles::Name, "name"}, |
||||
{Roles::Id, "roomid"}, |
||||
{Roles::AvatarUrl, "avatarUrl"}, |
||||
{Roles::Topic, "topic"}, |
||||
{Roles::MemberCount, "numMembers"}, |
||||
{Roles::Previewable, "canPreview"}, |
||||
}; |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::resetDisplayedData() |
||||
{ |
||||
beginResetModel(); |
||||
|
||||
prevBatch_ = ""; |
||||
nextBatch_ = ""; |
||||
canFetchMore_ = true; |
||||
|
||||
publicRoomsData_.clear(); |
||||
|
||||
endResetModel(); |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::setMatrixServer(const QString &s) |
||||
{ |
||||
server_ = s.toStdString(); |
||||
|
||||
nhlog::ui()->debug("Received matrix server: {}", server_); |
||||
|
||||
resetDisplayedData(); |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::setSearchTerm(const QString &f) |
||||
{ |
||||
userSearchString_ = f.toStdString(); |
||||
|
||||
nhlog::ui()->debug("Received user query: {}", userSearchString_); |
||||
|
||||
resetDisplayedData(); |
||||
} |
||||
|
||||
bool |
||||
RoomDirectoryModel::canJoinRoom(const QByteArray &room) |
||||
{ |
||||
const QString room_id(room); |
||||
return !room_id.isEmpty() && !cache::getRoomInfo({room_id.toStdString()}).count(room_id); |
||||
} |
||||
|
||||
std::vector<std::string> |
||||
RoomDirectoryModel::getViasForRoom(const std::vector<std::string> &aliases) |
||||
{ |
||||
std::vector<std::string> vias; |
||||
|
||||
vias.reserve(aliases.size()); |
||||
|
||||
std::transform(aliases.begin(), |
||||
aliases.end(), |
||||
std::back_inserter(vias), |
||||
[](const auto &alias) { return alias.substr(alias.find(":") + 1); }); |
||||
|
||||
return vias; |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::joinRoom(const int &index) |
||||
{ |
||||
if (index >= 0 && static_cast<size_t>(index) < publicRoomsData_.size()) { |
||||
const auto &chunk = publicRoomsData_[index]; |
||||
nhlog::ui()->debug("'Joining room {}", chunk.room_id); |
||||
ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases)); |
||||
} |
||||
} |
||||
|
||||
QVariant |
||||
RoomDirectoryModel::data(const QModelIndex &index, int role) const |
||||
{ |
||||
if (hasIndex(index.row(), index.column(), index.parent())) { |
||||
const auto &room_chunk = publicRoomsData_[index.row()]; |
||||
switch (role) { |
||||
case Roles::Name: |
||||
return QString::fromStdString(room_chunk.name); |
||||
case Roles::Id: |
||||
return QString::fromStdString(room_chunk.room_id); |
||||
case Roles::AvatarUrl: |
||||
return QString::fromStdString(room_chunk.avatar_url); |
||||
case Roles::Topic: |
||||
return QString::fromStdString(room_chunk.topic); |
||||
case Roles::MemberCount: |
||||
return QVariant::fromValue(room_chunk.num_joined_members); |
||||
case Roles::Previewable: |
||||
return QVariant::fromValue(room_chunk.world_readable); |
||||
} |
||||
} |
||||
return {}; |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::fetchMore(const QModelIndex &) |
||||
{ |
||||
if (!canFetchMore_) |
||||
return; |
||||
|
||||
nhlog::net()->debug("Fetching more rooms from mtxclient..."); |
||||
|
||||
mtx::requests::PublicRooms req; |
||||
req.limit = limit_; |
||||
req.since = prevBatch_; |
||||
req.filter.generic_search_term = userSearchString_; |
||||
// req.third_party_instance_id = third_party_instance_id;
|
||||
auto requested_server = server_; |
||||
|
||||
reachedEndOfPagination_ = false; |
||||
emit reachedEndOfPaginationChanged(); |
||||
|
||||
loadingMoreRooms_ = true; |
||||
emit loadingMoreRoomsChanged(); |
||||
|
||||
http::client()->post_public_rooms( |
||||
req, |
||||
[requested_server, this, req](const mtx::responses::PublicRooms &res, |
||||
mtx::http::RequestErr err) { |
||||
loadingMoreRooms_ = false; |
||||
emit loadingMoreRoomsChanged(); |
||||
|
||||
if (err) { |
||||
nhlog::net()->error( |
||||
"Failed to retrieve rooms from mtxclient - {} - {} - {}", |
||||
mtx::errors::to_string(err->matrix_error.errcode), |
||||
err->matrix_error.error, |
||||
err->parse_error); |
||||
} else if (req.filter.generic_search_term == this->userSearchString_ && |
||||
req.since == this->prevBatch_ && requested_server == this->server_) { |
||||
nhlog::net()->debug("signalling chunk to GUI thread"); |
||||
emit fetchedRoomsBatch(res.chunk, res.next_batch); |
||||
} |
||||
}, |
||||
requested_server); |
||||
} |
||||
|
||||
void |
||||
RoomDirectoryModel::displayRooms(std::vector<mtx::responses::PublicRoomsChunk> fetched_rooms, |
||||
const std::string &next_batch) |
||||
{ |
||||
nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch); |
||||
|
||||
if (fetched_rooms.empty()) { |
||||
nhlog::net()->error("mtxclient helper thread yielded empty chunk!"); |
||||
return; |
||||
} |
||||
|
||||
beginInsertRows(QModelIndex(), |
||||
static_cast<int>(publicRoomsData_.size()), |
||||
static_cast<int>(publicRoomsData_.size() + fetched_rooms.size()) - 1); |
||||
this->publicRoomsData_.insert( |
||||
this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end()); |
||||
endInsertRows(); |
||||
|
||||
if (next_batch.empty()) { |
||||
canFetchMore_ = false; |
||||
reachedEndOfPagination_ = true; |
||||
emit reachedEndOfPaginationChanged(); |
||||
} |
||||
|
||||
prevBatch_ = next_batch; |
||||
|
||||
nhlog::ui()->debug("Finished loading rooms"); |
||||
} |
@ -0,0 +1,96 @@ |
||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once |
||||
|
||||
#include <QAbstractListModel> |
||||
#include <QHash> |
||||
#include <QString> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "MatrixClient.h" |
||||
#include <mtx/responses/public_rooms.hpp> |
||||
#include <mtxclient/http/errors.hpp> |
||||
|
||||
#include "Logging.h" |
||||
|
||||
namespace mtx::http { |
||||
using RequestErr = const std::optional<mtx::http::ClientError> &; |
||||
} |
||||
namespace mtx::responses { |
||||
struct PublicRooms; |
||||
} |
||||
|
||||
class RoomDirectoryModel : public QAbstractListModel |
||||
{ |
||||
Q_OBJECT |
||||
|
||||
Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged) |
||||
Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY |
||||
reachedEndOfPaginationChanged) |
||||
|
||||
public: |
||||
explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &s = ""); |
||||
|
||||
enum Roles |
||||
{ |
||||
Name = Qt::UserRole, |
||||
Id, |
||||
AvatarUrl, |
||||
Topic, |
||||
MemberCount, |
||||
Previewable |
||||
}; |
||||
QHash<int, QByteArray> roleNames() const override; |
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override; |
||||
|
||||
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override |
||||
{ |
||||
(void)parent; |
||||
return static_cast<int>(publicRoomsData_.size()); |
||||
} |
||||
|
||||
bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } |
||||
|
||||
bool loadingMoreRooms() const { return loadingMoreRooms_; } |
||||
|
||||
bool reachedEndOfPagination() const { return reachedEndOfPagination_; } |
||||
|
||||
void fetchMore(const QModelIndex &) override; |
||||
|
||||
Q_INVOKABLE bool canJoinRoom(const QByteArray &room); |
||||
Q_INVOKABLE void joinRoom(const int &index = -1); |
||||
|
||||
signals: |
||||
void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> rooms, |
||||
const std::string &next_batch); |
||||
void loadingMoreRoomsChanged(); |
||||
void reachedEndOfPaginationChanged(); |
||||
|
||||
public slots: |
||||
void setMatrixServer(const QString &s = ""); |
||||
void setSearchTerm(const QString &f); |
||||
|
||||
private slots: |
||||
|
||||
void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> rooms, |
||||
const std::string &next_batch); |
||||
|
||||
private: |
||||
static constexpr size_t limit_ = 50; |
||||
|
||||
std::string server_; |
||||
std::string userSearchString_; |
||||
std::string prevBatch_; |
||||
std::string nextBatch_; |
||||
bool canFetchMore_{true}; |
||||
bool loadingMoreRooms_{false}; |
||||
bool reachedEndOfPagination_{false}; |
||||
std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_; |
||||
|
||||
std::vector<std::string> getViasForRoom(const std::vector<std::string> &room); |
||||
void resetDisplayedData(); |
||||
}; |
Loading…
Reference in new issue