mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 20:48:52 +03:00
Initial support for backwards pagination
This commit is contained in:
parent
ff611c1b39
commit
0368d854cf
10 changed files with 388 additions and 94 deletions
|
@ -91,6 +91,7 @@ set(SRC_FILES
|
||||||
src/MatrixClient.cc
|
src/MatrixClient.cc
|
||||||
src/Profile.cc
|
src/Profile.cc
|
||||||
src/RoomInfoListItem.cc
|
src/RoomInfoListItem.cc
|
||||||
|
src/RoomMessages.cc
|
||||||
src/RoomList.cc
|
src/RoomList.cc
|
||||||
src/RoomState.cc
|
src/RoomState.cc
|
||||||
src/Register.cc
|
src/Register.cc
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <QtNetwork/QNetworkAccessManager>
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
|
|
||||||
#include "Profile.h"
|
#include "Profile.h"
|
||||||
|
#include "RoomMessages.h"
|
||||||
#include "Sync.h"
|
#include "Sync.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -43,6 +44,7 @@ public:
|
||||||
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
|
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
|
||||||
void fetchOwnAvatar(const QUrl &avatar_url);
|
void fetchOwnAvatar(const QUrl &avatar_url);
|
||||||
void downloadImage(const QString &event_id, const QUrl &url);
|
void downloadImage(const QString &event_id, const QUrl &url);
|
||||||
|
void messages(const QString &room_id, const QString &from_token) noexcept;
|
||||||
|
|
||||||
inline QUrl getHomeServer();
|
inline QUrl getHomeServer();
|
||||||
inline int transactionId();
|
inline int transactionId();
|
||||||
|
@ -77,19 +79,21 @@ signals:
|
||||||
void syncCompleted(const SyncResponse &response);
|
void syncCompleted(const SyncResponse &response);
|
||||||
void syncFailed(const QString &msg);
|
void syncFailed(const QString &msg);
|
||||||
void messageSent(const QString &event_id, const QString &roomid, const int txn_id);
|
void messageSent(const QString &event_id, const QString &roomid, const int txn_id);
|
||||||
|
void messagesRetrieved(const QString &room_id, const RoomMessages &msgs);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onResponse(QNetworkReply *reply);
|
void onResponse(QNetworkReply *reply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Endpoint {
|
enum class Endpoint {
|
||||||
GetOwnProfile,
|
|
||||||
GetOwnAvatar,
|
GetOwnAvatar,
|
||||||
|
GetOwnProfile,
|
||||||
GetProfile,
|
GetProfile,
|
||||||
Image,
|
Image,
|
||||||
InitialSync,
|
InitialSync,
|
||||||
Login,
|
Login,
|
||||||
Logout,
|
Logout,
|
||||||
|
Messages,
|
||||||
Register,
|
Register,
|
||||||
RoomAvatar,
|
RoomAvatar,
|
||||||
SendTextMessage,
|
SendTextMessage,
|
||||||
|
@ -109,6 +113,7 @@ private:
|
||||||
void onSyncResponse(QNetworkReply *reply);
|
void onSyncResponse(QNetworkReply *reply);
|
||||||
void onRoomAvatarResponse(QNetworkReply *reply);
|
void onRoomAvatarResponse(QNetworkReply *reply);
|
||||||
void onImageResponse(QNetworkReply *reply);
|
void onImageResponse(QNetworkReply *reply);
|
||||||
|
void onMessagesResponse(QNetworkReply *reply);
|
||||||
|
|
||||||
// Client API prefix.
|
// Client API prefix.
|
||||||
QString api_url_;
|
QString api_url_;
|
||||||
|
|
56
include/RoomMessages.h
Normal file
56
include/RoomMessages.h
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ROOM_MESSAGES_H
|
||||||
|
#define ROOM_MESSAGES_H
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
|
||||||
|
#include "Deserializable.h"
|
||||||
|
|
||||||
|
class RoomMessages : public Deserializable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void deserialize(const QJsonDocument &data) override;
|
||||||
|
|
||||||
|
inline QString start() const;
|
||||||
|
inline QString end() const;
|
||||||
|
inline QJsonArray chunk() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString start_;
|
||||||
|
QString end_;
|
||||||
|
QJsonArray chunk_;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline QString RoomMessages::start() const
|
||||||
|
{
|
||||||
|
return start_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QString RoomMessages::end() const
|
||||||
|
{
|
||||||
|
return end_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QJsonArray RoomMessages::chunk() const
|
||||||
|
{
|
||||||
|
return chunk_;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ROOM_MESSAGES_H
|
|
@ -51,32 +51,50 @@ struct PendingMessage {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In which place new TimelineItems should be inserted.
|
||||||
|
enum class TimelineDirection {
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
};
|
||||||
|
|
||||||
class TimelineView : public QWidget
|
class TimelineView : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimelineView(QSharedPointer<MatrixClient> client, QWidget *parent = 0);
|
TimelineView(const Timeline &timeline, QSharedPointer<MatrixClient> client, const QString &room_id, QWidget *parent = 0);
|
||||||
TimelineView(const QJsonArray &events, QSharedPointer<MatrixClient> client, QWidget *parent = 0);
|
|
||||||
~TimelineView();
|
|
||||||
|
|
||||||
void addHistoryItem(const events::MessageEvent<msgs::Image> &e, const QString &color, bool with_sender);
|
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Image> &e, const QString &color, bool with_sender);
|
||||||
void addHistoryItem(const events::MessageEvent<msgs::Notice> &e, const QString &color, bool with_sender);
|
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Notice> &e, const QString &color, bool with_sender);
|
||||||
void addHistoryItem(const events::MessageEvent<msgs::Text> &e, const QString &color, bool with_sender);
|
TimelineItem *createTimelineItem(const events::MessageEvent<msgs::Text> &e, const QString &color, bool with_sender);
|
||||||
|
|
||||||
int addEvents(const QJsonArray &events);
|
// Add new events at the end of the timeline.
|
||||||
|
int addEvents(const Timeline &timeline);
|
||||||
void addUserTextMessage(const QString &msg, int txn_id);
|
void addUserTextMessage(const QString &msg, int txn_id);
|
||||||
void updatePendingMessage(int txn_id, QString event_id);
|
void updatePendingMessage(int txn_id, QString event_id);
|
||||||
|
void scrollDown();
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void sliderRangeChanged(int min, int max);
|
void sliderRangeChanged(int min, int max);
|
||||||
|
void sliderMoved(int position);
|
||||||
|
|
||||||
|
// Add old events at the top of the timeline.
|
||||||
|
void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void init();
|
void init();
|
||||||
void removePendingMessage(const events::MessageEvent<msgs::Text> &e);
|
void removePendingMessage(const events::MessageEvent<msgs::Text> &e);
|
||||||
|
void addTimelineItem(TimelineItem *item, TimelineDirection direction);
|
||||||
|
void updateLastSender(const QString &user_id, TimelineDirection direction);
|
||||||
|
|
||||||
|
// Used to determine whether or not we should prefix a message with the sender's name.
|
||||||
|
bool isSenderRendered(const QString &user_id, TimelineDirection direction);
|
||||||
bool isPendingMessage(const events::MessageEvent<msgs::Text> &e, const QString &userid);
|
bool isPendingMessage(const events::MessageEvent<msgs::Text> &e, const QString &userid);
|
||||||
|
|
||||||
|
// Return nullptr if the event couldn't be parsed.
|
||||||
|
TimelineItem *parseMessageEvent(const QJsonObject &event, TimelineDirection direction);
|
||||||
|
|
||||||
QVBoxLayout *top_layout_;
|
QVBoxLayout *top_layout_;
|
||||||
QVBoxLayout *scroll_layout_;
|
QVBoxLayout *scroll_layout_;
|
||||||
|
|
||||||
|
@ -84,6 +102,19 @@ private:
|
||||||
QWidget *scroll_widget_;
|
QWidget *scroll_widget_;
|
||||||
|
|
||||||
QString last_sender_;
|
QString last_sender_;
|
||||||
|
QString last_sender_backwards_;
|
||||||
|
QString room_id_;
|
||||||
|
QString prev_batch_token_;
|
||||||
|
QString local_user_;
|
||||||
|
|
||||||
|
bool isPaginationInProgress_ = false;
|
||||||
|
bool isInitialized = false;
|
||||||
|
bool isTimelineFinished = false;
|
||||||
|
|
||||||
|
const int SCROLL_BAR_GAP = 300;
|
||||||
|
|
||||||
|
int scroll_height_ = 0;
|
||||||
|
int previous_max_height_ = 0;
|
||||||
|
|
||||||
QList<PendingMessage> pending_msgs_;
|
QList<PendingMessage> pending_msgs_;
|
||||||
QSharedPointer<MatrixClient> client_;
|
QSharedPointer<MatrixClient> client_;
|
||||||
|
|
|
@ -108,6 +108,7 @@ void MainWindow::showChatPage(QString userid, QString homeserver, QString token)
|
||||||
if (progress_modal_ == nullptr) {
|
if (progress_modal_ == nullptr) {
|
||||||
progress_modal_ = new OverlayModal(this, spinner_);
|
progress_modal_ = new OverlayModal(this, spinner_);
|
||||||
progress_modal_->fadeIn();
|
progress_modal_->fadeIn();
|
||||||
|
progress_modal_->setDuration(300);
|
||||||
}
|
}
|
||||||
|
|
||||||
login_page_->reset();
|
login_page_->reset();
|
||||||
|
|
|
@ -333,6 +333,32 @@ void MatrixClient::onImageResponse(QNetworkReply *reply)
|
||||||
emit imageDownloaded(event_id, pixmap);
|
emit imageDownloaded(event_id, pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MatrixClient::onMessagesResponse(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
if (status == 0 || status >= 400) {
|
||||||
|
qWarning() << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = reply->readAll();
|
||||||
|
auto room_id = reply->property("room_id").toString();
|
||||||
|
|
||||||
|
RoomMessages msgs;
|
||||||
|
|
||||||
|
try {
|
||||||
|
msgs.deserialize(QJsonDocument::fromJson(data));
|
||||||
|
} catch (const DeserializationException &e) {
|
||||||
|
qWarning() << "Room messages from" << room_id << e.what();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit messagesRetrieved(room_id, msgs);
|
||||||
|
}
|
||||||
|
|
||||||
void MatrixClient::onResponse(QNetworkReply *reply)
|
void MatrixClient::onResponse(QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
switch (static_cast<Endpoint>(reply->property("endpoint").toInt())) {
|
switch (static_cast<Endpoint>(reply->property("endpoint").toInt())) {
|
||||||
|
@ -369,6 +395,9 @@ void MatrixClient::onResponse(QNetworkReply *reply)
|
||||||
case Endpoint::GetOwnAvatar:
|
case Endpoint::GetOwnAvatar:
|
||||||
onGetOwnAvatarResponse(reply);
|
onGetOwnAvatarResponse(reply);
|
||||||
break;
|
break;
|
||||||
|
case Endpoint::Messages:
|
||||||
|
onMessagesResponse(reply);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -581,3 +610,21 @@ void MatrixClient::fetchOwnAvatar(const QUrl &avatar_url)
|
||||||
QNetworkReply *reply = get(avatar_request);
|
QNetworkReply *reply = get(avatar_request);
|
||||||
reply->setProperty("endpoint", static_cast<int>(Endpoint::GetOwnAvatar));
|
reply->setProperty("endpoint", static_cast<int>(Endpoint::GetOwnAvatar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MatrixClient::messages(const QString &room_id, const QString &from_token) noexcept
|
||||||
|
{
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("access_token", token_);
|
||||||
|
query.addQueryItem("from", from_token);
|
||||||
|
query.addQueryItem("dir", "b");
|
||||||
|
|
||||||
|
QUrl endpoint(server_);
|
||||||
|
endpoint.setPath(api_url_ + QString("/rooms/%1/messages").arg(room_id));
|
||||||
|
endpoint.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest request(QString(endpoint.toEncoded()));
|
||||||
|
|
||||||
|
QNetworkReply *reply = get(request);
|
||||||
|
reply->setProperty("endpoint", static_cast<int>(Endpoint::Messages));
|
||||||
|
reply->setProperty("room_id", room_id);
|
||||||
|
}
|
||||||
|
|
42
src/RoomMessages.cc
Normal file
42
src/RoomMessages.cc
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "RoomMessages.h"
|
||||||
|
|
||||||
|
void RoomMessages::deserialize(const QJsonDocument &data)
|
||||||
|
{
|
||||||
|
if (!data.isObject())
|
||||||
|
throw DeserializationException("response is not a JSON object");
|
||||||
|
|
||||||
|
QJsonObject object = data.object();
|
||||||
|
|
||||||
|
if (!object.contains("start"))
|
||||||
|
throw DeserializationException("start key is missing");
|
||||||
|
|
||||||
|
if (!object.contains("end"))
|
||||||
|
throw DeserializationException("end key is missing");
|
||||||
|
|
||||||
|
if (!object.contains("chunk"))
|
||||||
|
throw DeserializationException("chunk key is missing");
|
||||||
|
|
||||||
|
if (!object.value("chunk").isArray())
|
||||||
|
throw DeserializationException("chunk isn't a JSON array");
|
||||||
|
|
||||||
|
start_ = object.value("start").toString();
|
||||||
|
end_ = object.value("end").toString();
|
||||||
|
chunk_ = object.value("chunk").toArray();
|
||||||
|
}
|
|
@ -34,19 +34,19 @@
|
||||||
namespace events = matrix::events;
|
namespace events = matrix::events;
|
||||||
namespace msgs = matrix::events::messages;
|
namespace msgs = matrix::events::messages;
|
||||||
|
|
||||||
TimelineView::TimelineView(const QJsonArray &events, QSharedPointer<MatrixClient> client, QWidget *parent)
|
TimelineView::TimelineView(const Timeline &timeline,
|
||||||
|
QSharedPointer<MatrixClient> client,
|
||||||
|
const QString &room_id,
|
||||||
|
QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
, room_id_{room_id}
|
||||||
, client_{client}
|
, client_{client}
|
||||||
{
|
{
|
||||||
init();
|
QSettings settings;
|
||||||
addEvents(events);
|
local_user_ = settings.value("auth/user_id").toString();
|
||||||
}
|
|
||||||
|
|
||||||
TimelineView::TimelineView(QSharedPointer<MatrixClient> client, QWidget *parent)
|
|
||||||
: QWidget(parent)
|
|
||||||
, client_{client}
|
|
||||||
{
|
|
||||||
init();
|
init();
|
||||||
|
addEvents(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineView::clear()
|
void TimelineView::clear()
|
||||||
|
@ -58,83 +58,175 @@ void TimelineView::clear()
|
||||||
void TimelineView::sliderRangeChanged(int min, int max)
|
void TimelineView::sliderRangeChanged(int min, int max)
|
||||||
{
|
{
|
||||||
Q_UNUSED(min);
|
Q_UNUSED(min);
|
||||||
scroll_area_->verticalScrollBar()->setValue(max);
|
|
||||||
|
if (!scroll_area_->verticalScrollBar()->isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (max - scroll_area_->verticalScrollBar()->value() < SCROLL_BAR_GAP)
|
||||||
|
scroll_area_->verticalScrollBar()->setValue(max);
|
||||||
}
|
}
|
||||||
|
|
||||||
int TimelineView::addEvents(const QJsonArray &events)
|
void TimelineView::scrollDown()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
int current = scroll_area_->verticalScrollBar()->value();
|
||||||
auto local_user = settings.value("auth/user_id").toString();
|
int max = scroll_area_->verticalScrollBar()->maximum();
|
||||||
|
|
||||||
int message_count = 0;
|
// The first time we enter the room move the scroll bar to the bottom.
|
||||||
events::EventType ty;
|
if (!isInitialized) {
|
||||||
|
scroll_area_->ensureVisible(0, scroll_widget_->size().height(), 0, 0);
|
||||||
|
isInitialized = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto &event : events) {
|
// If the gap is small enough move the scroll bar down. e.g when a new message appears.
|
||||||
ty = events::extractEventType(event.toObject());
|
if (max - current < SCROLL_BAR_GAP)
|
||||||
|
scroll_area_->verticalScrollBar()->setValue(max);
|
||||||
|
}
|
||||||
|
|
||||||
if (ty == events::EventType::RoomMessage) {
|
void TimelineView::sliderMoved(int position)
|
||||||
events::MessageEventType msg_type = events::extractMessageEventType(event.toObject());
|
{
|
||||||
|
if (!scroll_area_->verticalScrollBar()->isVisible())
|
||||||
|
return;
|
||||||
|
|
||||||
if (msg_type == events::MessageEventType::Text) {
|
// The scrollbar is high enough so we can start retrieving old events.
|
||||||
events::MessageEvent<msgs::Text> text;
|
if (position < SCROLL_BAR_GAP) {
|
||||||
|
if (isTimelineFinished)
|
||||||
|
return;
|
||||||
|
|
||||||
try {
|
// Prevent user from moving up when there is pagination in progress.
|
||||||
text.deserialize(event.toObject());
|
if (isPaginationInProgress_) {
|
||||||
} catch (const DeserializationException &e) {
|
scroll_area_->verticalScrollBar()->setValue(SCROLL_BAR_GAP);
|
||||||
qWarning() << e.what() << event;
|
return;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isPendingMessage(text, local_user)) {
|
isPaginationInProgress_ = true;
|
||||||
removePendingMessage(text);
|
scroll_height_ = scroll_area_->verticalScrollBar()->value();
|
||||||
continue;
|
previous_max_height_ = scroll_area_->verticalScrollBar()->maximum();
|
||||||
}
|
|
||||||
|
|
||||||
auto with_sender = last_sender_ != text.sender();
|
// FIXME: Maybe move this to TimelineViewManager to remove the extra calls?
|
||||||
auto color = TimelineViewManager::getUserColor(text.sender());
|
client_.data()->messages(room_id_, prev_batch_token_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addHistoryItem(text, color, with_sender);
|
void TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages &msgs)
|
||||||
last_sender_ = text.sender();
|
{
|
||||||
|
if (room_id_ != room_id)
|
||||||
|
return;
|
||||||
|
|
||||||
message_count += 1;
|
if (msgs.chunk().count() == 0) {
|
||||||
} else if (msg_type == events::MessageEventType::Notice) {
|
isTimelineFinished = true;
|
||||||
events::MessageEvent<msgs::Notice> notice;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
isTimelineFinished = false;
|
||||||
notice.deserialize(event.toObject());
|
last_sender_backwards_.clear();
|
||||||
} catch (const DeserializationException &e) {
|
QList<TimelineItem *> items;
|
||||||
qWarning() << e.what() << event;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto with_sender = last_sender_ != notice.sender();
|
// Parse in reverse order to determine where we should not show sender's name.
|
||||||
auto color = TimelineViewManager::getUserColor(notice.sender());
|
auto it = msgs.chunk().constEnd();
|
||||||
|
while (it != msgs.chunk().constBegin()) {
|
||||||
|
--it;
|
||||||
|
|
||||||
addHistoryItem(notice, color, with_sender);
|
TimelineItem *item = parseMessageEvent((*it).toObject(), TimelineDirection::Top);
|
||||||
last_sender_ = notice.sender();
|
|
||||||
|
|
||||||
message_count += 1;
|
if (item != nullptr)
|
||||||
} else if (msg_type == events::MessageEventType::Image) {
|
items.push_back(item);
|
||||||
events::MessageEvent<msgs::Image> img;
|
}
|
||||||
|
|
||||||
try {
|
// Reverse again to render them.
|
||||||
img.deserialize(event.toObject());
|
std::reverse(items.begin(), items.end());
|
||||||
} catch (const DeserializationException &e) {
|
|
||||||
qWarning() << e.what() << event;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto with_sender = last_sender_ != img.sender();
|
for (const auto &item : items)
|
||||||
auto color = TimelineViewManager::getUserColor(img.sender());
|
addTimelineItem(item, TimelineDirection::Top);
|
||||||
|
|
||||||
addHistoryItem(img, color, with_sender);
|
prev_batch_token_ = msgs.end();
|
||||||
|
isPaginationInProgress_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
last_sender_ = img.sender();
|
TimelineItem *TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection direction)
|
||||||
message_count += 1;
|
{
|
||||||
} else if (msg_type == events::MessageEventType::Unknown) {
|
events::EventType ty = events::extractEventType(event);
|
||||||
qWarning() << "Unknown message type" << event.toObject();
|
|
||||||
continue;
|
if (ty == events::EventType::RoomMessage) {
|
||||||
|
events::MessageEventType msg_type = events::extractMessageEventType(event);
|
||||||
|
|
||||||
|
if (msg_type == events::MessageEventType::Text) {
|
||||||
|
events::MessageEvent<msgs::Text> text;
|
||||||
|
|
||||||
|
try {
|
||||||
|
text.deserialize(event);
|
||||||
|
} catch (const DeserializationException &e) {
|
||||||
|
qWarning() << e.what() << event;
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPendingMessage(text, local_user_)) {
|
||||||
|
removePendingMessage(text);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto with_sender = isSenderRendered(text.sender(), direction);
|
||||||
|
updateLastSender(text.sender(), direction);
|
||||||
|
|
||||||
|
auto color = TimelineViewManager::getUserColor(text.sender());
|
||||||
|
last_sender_ = text.sender();
|
||||||
|
|
||||||
|
return createTimelineItem(text, color, with_sender);
|
||||||
|
} else if (msg_type == events::MessageEventType::Notice) {
|
||||||
|
events::MessageEvent<msgs::Notice> notice;
|
||||||
|
|
||||||
|
try {
|
||||||
|
notice.deserialize(event);
|
||||||
|
} catch (const DeserializationException &e) {
|
||||||
|
qWarning() << e.what() << event;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto with_sender = isSenderRendered(notice.sender(), direction);
|
||||||
|
updateLastSender(notice.sender(), direction);
|
||||||
|
|
||||||
|
auto color = TimelineViewManager::getUserColor(notice.sender());
|
||||||
|
last_sender_ = notice.sender();
|
||||||
|
|
||||||
|
return createTimelineItem(notice, color, with_sender);
|
||||||
|
} else if (msg_type == events::MessageEventType::Image) {
|
||||||
|
events::MessageEvent<msgs::Image> img;
|
||||||
|
|
||||||
|
try {
|
||||||
|
img.deserialize(event);
|
||||||
|
} catch (const DeserializationException &e) {
|
||||||
|
qWarning() << e.what() << event;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto with_sender = isSenderRendered(img.sender(), direction);
|
||||||
|
updateLastSender(img.sender(), direction);
|
||||||
|
|
||||||
|
auto color = TimelineViewManager::getUserColor(img.sender());
|
||||||
|
last_sender_ = img.sender();
|
||||||
|
|
||||||
|
return createTimelineItem(img, color, with_sender);
|
||||||
|
} else if (msg_type == events::MessageEventType::Unknown) {
|
||||||
|
qWarning() << "Unknown message type" << event;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TimelineView::addEvents(const Timeline &timeline)
|
||||||
|
{
|
||||||
|
int message_count = 0;
|
||||||
|
|
||||||
|
prev_batch_token_ = timeline.previousBatch();
|
||||||
|
|
||||||
|
for (const auto &event : timeline.events()) {
|
||||||
|
TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom);
|
||||||
|
|
||||||
|
if (item != nullptr) {
|
||||||
|
message_count += 1;
|
||||||
|
addTimelineItem(item, TimelineDirection::Bottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,35 +257,59 @@ void TimelineView::init()
|
||||||
|
|
||||||
setLayout(top_layout_);
|
setLayout(top_layout_);
|
||||||
|
|
||||||
connect(scroll_area_->verticalScrollBar(),
|
connect(client_.data(), &MatrixClient::messagesRetrieved, this, &TimelineView::addBackwardsEvents);
|
||||||
SIGNAL(rangeChanged(int, int)),
|
|
||||||
this,
|
connect(scroll_area_->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(sliderMoved(int)));
|
||||||
SLOT(sliderRangeChanged(int, int)));
|
connect(scroll_area_->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(sliderRangeChanged(int, int)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineView::addHistoryItem(const events::MessageEvent<msgs::Image> &event, const QString &color, bool with_sender)
|
void TimelineView::updateLastSender(const QString &user_id, TimelineDirection direction)
|
||||||
|
{
|
||||||
|
if (direction == TimelineDirection::Bottom)
|
||||||
|
last_sender_ = user_id;
|
||||||
|
else
|
||||||
|
last_sender_backwards_ = user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TimelineView::isSenderRendered(const QString &user_id, TimelineDirection direction)
|
||||||
|
{
|
||||||
|
if (direction == TimelineDirection::Bottom)
|
||||||
|
return last_sender_ != user_id;
|
||||||
|
else
|
||||||
|
return last_sender_backwards_ != user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimelineItem *TimelineView::createTimelineItem(const events::MessageEvent<msgs::Image> &event, const QString &color, bool with_sender)
|
||||||
{
|
{
|
||||||
auto image = new ImageItem(client_, event);
|
auto image = new ImageItem(client_, event);
|
||||||
|
|
||||||
if (with_sender) {
|
if (with_sender) {
|
||||||
auto item = new TimelineItem(image, event, color, scroll_widget_);
|
auto item = new TimelineItem(image, event, color, scroll_widget_);
|
||||||
scroll_layout_->addWidget(item);
|
return item;
|
||||||
} else {
|
|
||||||
auto item = new TimelineItem(image, event, scroll_widget_);
|
|
||||||
scroll_layout_->addWidget(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto item = new TimelineItem(image, event, scroll_widget_);
|
||||||
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineView::addHistoryItem(const events::MessageEvent<msgs::Notice> &event, const QString &color, bool with_sender)
|
TimelineItem *TimelineView::createTimelineItem(const events::MessageEvent<msgs::Notice> &event, const QString &color, bool with_sender)
|
||||||
{
|
{
|
||||||
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
|
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
|
||||||
scroll_layout_->addWidget(item);
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineView::addHistoryItem(const events::MessageEvent<msgs::Text> &event, const QString &color, bool with_sender)
|
TimelineItem *TimelineView::createTimelineItem(const events::MessageEvent<msgs::Text> &event, const QString &color, bool with_sender)
|
||||||
{
|
{
|
||||||
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
|
TimelineItem *item = new TimelineItem(event, with_sender, color, scroll_widget_);
|
||||||
scroll_layout_->addWidget(item);
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction)
|
||||||
|
{
|
||||||
|
if (direction == TimelineDirection::Bottom)
|
||||||
|
scroll_layout_->addWidget(item);
|
||||||
|
else
|
||||||
|
scroll_layout_->insertWidget(0, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineView::updatePendingMessage(int txn_id, QString event_id)
|
void TimelineView::updatePendingMessage(int txn_id, QString event_id)
|
||||||
|
@ -254,7 +370,3 @@ void TimelineView::addUserTextMessage(const QString &body, int txn_id)
|
||||||
|
|
||||||
pending_msgs_.push_back(message);
|
pending_msgs_.push_back(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineView::~TimelineView()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
|
@ -78,10 +78,9 @@ void TimelineViewManager::initialize(const Rooms &rooms)
|
||||||
{
|
{
|
||||||
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
|
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
|
||||||
auto roomid = it.key();
|
auto roomid = it.key();
|
||||||
auto events = it.value().timeline().events();
|
|
||||||
|
|
||||||
// Create a history view with the room events.
|
// Create a history view with the room events.
|
||||||
TimelineView *view = new TimelineView(events, client_);
|
TimelineView *view = new TimelineView(it.value().timeline(), client_, it.key());
|
||||||
views_.insert(it.key(), view);
|
views_.insert(it.key(), view);
|
||||||
|
|
||||||
// Add the view in the widget stack.
|
// Add the view in the widget stack.
|
||||||
|
@ -100,9 +99,8 @@ void TimelineViewManager::sync(const Rooms &rooms)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto view = views_.value(roomid);
|
auto view = views_.value(roomid);
|
||||||
auto events = it.value().timeline().events();
|
|
||||||
|
|
||||||
int msgs_added = view->addEvents(events);
|
int msgs_added = view->addEvents(it.value().timeline());
|
||||||
|
|
||||||
if (msgs_added > 0) {
|
if (msgs_added > 0) {
|
||||||
// TODO: When the app window gets active the current
|
// TODO: When the app window gets active the current
|
||||||
|
@ -124,6 +122,7 @@ void TimelineViewManager::setHistoryView(const QString &room_id)
|
||||||
|
|
||||||
active_room_ = room_id;
|
active_room_ = room_id;
|
||||||
auto widget = views_.value(room_id);
|
auto widget = views_.value(room_id);
|
||||||
|
widget->scrollDown();
|
||||||
|
|
||||||
setCurrentWidget(widget);
|
setCurrentWidget(widget);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
app.setStyleSheet(
|
app.setStyleSheet(
|
||||||
"QScrollBar:vertical { background-color: #f8fbfe; width: 8px; border: none; margin: 2px; }"
|
"QScrollBar:vertical { background-color: #f8fbfe; width: 8px; border: none; margin: 2px; }"
|
||||||
"QScrollBar::handle:vertical { background-color : #d6dde3; }"
|
"QScrollBar::handle:vertical { min-height: 40px; background-color : #d6dde3; }"
|
||||||
"QScrollBar::add-line:vertical { border: none; background: none; }"
|
"QScrollBar::add-line:vertical { border: none; background: none; }"
|
||||||
"QScrollBar::sub-line:vertical { border: none; background: none; }");
|
"QScrollBar::sub-line:vertical { border: none; background: none; }");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue