mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Add loading spinner and restore message send queue
This commit is contained in:
parent
1659356839
commit
c424e397b0
5 changed files with 123 additions and 45 deletions
|
@ -19,13 +19,20 @@ Item {
|
||||||
color: colors.window
|
color: colors.window
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
visible: !timelineManager.timeline
|
visible: !timelineManager.timeline && !timelineManager.isInitialSync
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: qsTr("No room open")
|
text: qsTr("No room open")
|
||||||
font.pointSize: 24
|
font.pointSize: 24
|
||||||
color: colors.windowText
|
color: colors.windowText
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
running: timelineManager.isInitialSync
|
||||||
|
height: 200
|
||||||
|
width: 200
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: chat
|
id: chat
|
||||||
|
|
||||||
|
@ -47,10 +54,6 @@ Item {
|
||||||
} else {
|
} else {
|
||||||
positionViewAtIndex(model.currentIndex, ListView.End)
|
positionViewAtIndex(model.currentIndex, ListView.End)
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (contentHeight < height) {
|
|
||||||
// model.fetchHistory();
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -332,16 +332,18 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
connect(
|
connect(
|
||||||
this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents);
|
this, &TimelineModel::oldMessagesRetrieved, this, &TimelineModel::addBackwardsEvents);
|
||||||
connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) {
|
connect(this, &TimelineModel::messageFailed, this, [this](QString txn_id) {
|
||||||
pending.remove(txn_id);
|
pending.removeOne(txn_id);
|
||||||
failed.insert(txn_id);
|
failed.insert(txn_id);
|
||||||
int idx = idToIndex(txn_id);
|
int idx = idToIndex(txn_id);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
nhlog::ui()->warn("Failed index out of range");
|
nhlog::ui()->warn("Failed index out of range");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isProcessingPending = false;
|
||||||
emit dataChanged(index(idx, 0), index(idx, 0));
|
emit dataChanged(index(idx, 0), index(idx, 0));
|
||||||
});
|
});
|
||||||
connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) {
|
connect(this, &TimelineModel::messageSent, this, [this](QString txn_id, QString event_id) {
|
||||||
|
pending.removeOne(txn_id);
|
||||||
int idx = idToIndex(txn_id);
|
int idx = idToIndex(txn_id);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
nhlog::ui()->warn("Sent index out of range");
|
nhlog::ui()->warn("Sent index out of range");
|
||||||
|
@ -365,11 +367,19 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
// ask to be notified for read receipts
|
// ask to be notified for read receipts
|
||||||
cache::client()->addPendingReceipt(room_id_, event_id);
|
cache::client()->addPendingReceipt(room_id_, event_id);
|
||||||
|
|
||||||
|
isProcessingPending = false;
|
||||||
emit dataChanged(index(idx, 0), index(idx, 0));
|
emit dataChanged(index(idx, 0), index(idx, 0));
|
||||||
|
|
||||||
|
if (pending.size() > 0)
|
||||||
|
emit nextPendingMessage();
|
||||||
});
|
});
|
||||||
connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) {
|
connect(this, &TimelineModel::redactionFailed, this, [](const QString &msg) {
|
||||||
emit ChatPage::instance()->showNotification(msg);
|
emit ChatPage::instance()->showNotification(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(
|
||||||
|
this, &TimelineModel::nextPendingMessage, this, &TimelineModel::processOnePendingMessage);
|
||||||
|
connect(this, &TimelineModel::newMessageToSend, this, &TimelineModel::addPendingMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray>
|
QHash<int, QByteArray>
|
||||||
|
@ -1035,6 +1045,7 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::db()->critical(
|
nhlog::db()->critical(
|
||||||
"failed to save megolm outbound session: {}", e.what());
|
"failed to save megolm outbound session: {}", e.what());
|
||||||
|
emit messageFailed(QString::fromStdString(txn_id));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1044,13 +1055,14 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
|
||||||
|
|
||||||
http::client()->query_keys(
|
http::client()->query_keys(
|
||||||
req,
|
req,
|
||||||
[keeper = std::move(keeper), megolm_payload, this](
|
[keeper = std::move(keeper), megolm_payload, txn_id, this](
|
||||||
const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
|
const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
nhlog::net()->warn("failed to query device keys: {} {}",
|
nhlog::net()->warn("failed to query device keys: {} {}",
|
||||||
err->matrix_error.error,
|
err->matrix_error.error,
|
||||||
static_cast<int>(err->status_code));
|
static_cast<int>(err->status_code));
|
||||||
// TODO: Mark the event as failed. Communicate with the UI.
|
// TODO: Mark the event as failed. Communicate with the UI.
|
||||||
|
emit messageFailed(QString::fromStdString(txn_id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1150,9 +1162,11 @@ TimelineModel::sendEncryptedMessage(const std::string &txn_id, nlohmann::json co
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::db()->critical(
|
nhlog::db()->critical(
|
||||||
"failed to open outbound megolm session ({}): {}", room_id, e.what());
|
"failed to open outbound megolm session ({}): {}", room_id, e.what());
|
||||||
|
emit messageFailed(QString::fromStdString(txn_id));
|
||||||
} catch (const mtx::crypto::olm_exception &e) {
|
} catch (const mtx::crypto::olm_exception &e) {
|
||||||
nhlog::crypto()->critical(
|
nhlog::crypto()->critical(
|
||||||
"failed to open outbound megolm session ({}): {}", room_id, e.what());
|
"failed to open outbound megolm session ({}): {}", room_id, e.what());
|
||||||
|
emit messageFailed(QString::fromStdString(txn_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1241,3 +1255,82 @@ TimelineModel::handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
|
||||||
(void)keeper;
|
(void)keeper;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SendMessageVisitor
|
||||||
|
{
|
||||||
|
SendMessageVisitor(const QString &txn_id, TimelineModel *model)
|
||||||
|
: txn_id_qstr_(txn_id)
|
||||||
|
, model_(model)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void operator()(const mtx::events::Event<T> &)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template<typename T,
|
||||||
|
std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
|
||||||
|
void operator()(const mtx::events::RoomEvent<T> &msg)
|
||||||
|
|
||||||
|
{
|
||||||
|
if (cache::client()->isRoomEncrypted(model_->room_id_.toStdString())) {
|
||||||
|
model_->sendEncryptedMessage(txn_id_qstr_.toStdString(),
|
||||||
|
nlohmann::json(msg.content));
|
||||||
|
} else {
|
||||||
|
QString txn_id_qstr = txn_id_qstr_;
|
||||||
|
TimelineModel *model = model_;
|
||||||
|
http::client()->send_room_message<T, mtx::events::EventType::RoomMessage>(
|
||||||
|
model->room_id_.toStdString(),
|
||||||
|
txn_id_qstr.toStdString(),
|
||||||
|
msg.content,
|
||||||
|
[txn_id_qstr, model](const mtx::responses::EventId &res,
|
||||||
|
mtx::http::RequestErr err) {
|
||||||
|
if (err) {
|
||||||
|
const int status_code =
|
||||||
|
static_cast<int>(err->status_code);
|
||||||
|
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
||||||
|
txn_id_qstr.toStdString(),
|
||||||
|
err->matrix_error.error,
|
||||||
|
status_code);
|
||||||
|
emit model->messageFailed(txn_id_qstr);
|
||||||
|
}
|
||||||
|
emit model->messageSent(
|
||||||
|
txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString txn_id_qstr_;
|
||||||
|
TimelineModel *model_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineModel::processOnePendingMessage()
|
||||||
|
{
|
||||||
|
if (isProcessingPending || pending.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
isProcessingPending = true;
|
||||||
|
|
||||||
|
QString txn_id_qstr = pending.first();
|
||||||
|
|
||||||
|
boost::apply_visitor(SendMessageVisitor{txn_id_qstr, this}, events.value(txn_id_qstr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
|
||||||
|
{
|
||||||
|
internalAddEvents({event});
|
||||||
|
|
||||||
|
QString txn_id_qstr =
|
||||||
|
boost::apply_visitor([](const auto &e) -> QString { return eventId(e); }, event);
|
||||||
|
beginInsertRows(QModelIndex(),
|
||||||
|
static_cast<int>(this->eventOrder.size()),
|
||||||
|
static_cast<int>(this->eventOrder.size()));
|
||||||
|
pending.push_back(txn_id_qstr);
|
||||||
|
this->eventOrder.insert(this->eventOrder.end(), txn_id_qstr);
|
||||||
|
endInsertRows();
|
||||||
|
updateLastMessage();
|
||||||
|
|
||||||
|
if (!isProcessingPending)
|
||||||
|
emit nextPendingMessage();
|
||||||
|
}
|
||||||
|
|
|
@ -173,6 +173,8 @@ public slots:
|
||||||
private slots:
|
private slots:
|
||||||
// Add old events at the top of the timeline.
|
// Add old events at the top of the timeline.
|
||||||
void addBackwardsEvents(const mtx::responses::Messages &msgs);
|
void addBackwardsEvents(const mtx::responses::Messages &msgs);
|
||||||
|
void processOnePendingMessage();
|
||||||
|
void addPendingMessage(mtx::events::collections::TimelineEvents event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void oldMessagesRetrieved(const mtx::responses::Messages &res);
|
void oldMessagesRetrieved(const mtx::responses::Messages &res);
|
||||||
|
@ -181,6 +183,8 @@ signals:
|
||||||
void currentIndexChanged(int index);
|
void currentIndexChanged(int index);
|
||||||
void redactionFailed(QString id);
|
void redactionFailed(QString id);
|
||||||
void eventRedacted(QString id);
|
void eventRedacted(QString id);
|
||||||
|
void nextPendingMessage();
|
||||||
|
void newMessageToSend(mtx::events::collections::TimelineEvents event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DecryptionResult decryptEvent(
|
DecryptionResult decryptEvent(
|
||||||
|
@ -198,7 +202,8 @@ private:
|
||||||
void readEvent(const std::string &id);
|
void readEvent(const std::string &id);
|
||||||
|
|
||||||
QHash<QString, mtx::events::collections::TimelineEvents> events;
|
QHash<QString, mtx::events::collections::TimelineEvents> events;
|
||||||
QSet<QString> pending, failed, read;
|
QSet<QString> failed, read;
|
||||||
|
QList<QString> pending;
|
||||||
std::vector<QString> eventOrder;
|
std::vector<QString> eventOrder;
|
||||||
|
|
||||||
QString room_id_;
|
QString room_id_;
|
||||||
|
@ -206,11 +211,14 @@ private:
|
||||||
|
|
||||||
bool isInitialSync = true;
|
bool isInitialSync = true;
|
||||||
bool paginationInProgress = false;
|
bool paginationInProgress = false;
|
||||||
|
bool isProcessingPending = false;
|
||||||
|
|
||||||
QHash<QString, QColor> userColors;
|
QHash<QString, QColor> userColors;
|
||||||
QString currentId;
|
QString currentId;
|
||||||
|
|
||||||
TimelineViewManager *manager_;
|
TimelineViewManager *manager_;
|
||||||
|
|
||||||
|
friend struct SendMessageVisitor;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
@ -224,35 +232,6 @@ TimelineModel::sendMessage(const T &msg)
|
||||||
msgCopy.event_id = txn_id;
|
msgCopy.event_id = txn_id;
|
||||||
msgCopy.sender = http::client()->user_id().to_string();
|
msgCopy.sender = http::client()->user_id().to_string();
|
||||||
msgCopy.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
|
msgCopy.origin_server_ts = QDateTime::currentMSecsSinceEpoch();
|
||||||
internalAddEvents({msgCopy});
|
|
||||||
|
|
||||||
QString txn_id_qstr = QString::fromStdString(txn_id);
|
emit newMessageToSend(msgCopy);
|
||||||
beginInsertRows(QModelIndex(),
|
|
||||||
static_cast<int>(this->eventOrder.size()),
|
|
||||||
static_cast<int>(this->eventOrder.size()));
|
|
||||||
pending.insert(txn_id_qstr);
|
|
||||||
this->eventOrder.insert(this->eventOrder.end(), txn_id_qstr);
|
|
||||||
endInsertRows();
|
|
||||||
updateLastMessage();
|
|
||||||
|
|
||||||
if (cache::client()->isRoomEncrypted(room_id_.toStdString()))
|
|
||||||
sendEncryptedMessage(txn_id, nlohmann::json(msg));
|
|
||||||
else
|
|
||||||
http::client()->send_room_message<T, mtx::events::EventType::RoomMessage>(
|
|
||||||
room_id_.toStdString(),
|
|
||||||
txn_id,
|
|
||||||
msg,
|
|
||||||
[this, txn_id, txn_id_qstr](const mtx::responses::EventId &res,
|
|
||||||
mtx::http::RequestErr err) {
|
|
||||||
if (err) {
|
|
||||||
const int status_code = static_cast<int>(err->status_code);
|
|
||||||
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
|
||||||
txn_id,
|
|
||||||
err->matrix_error.error,
|
|
||||||
status_code);
|
|
||||||
emit messageFailed(txn_id_qstr);
|
|
||||||
}
|
|
||||||
emit messageSent(txn_id_qstr,
|
|
||||||
QString::fromStdString(res.event_id.to_string()));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,9 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
|
||||||
addRoom(QString::fromStdString(it->first));
|
addRoom(QString::fromStdString(it->first));
|
||||||
models.value(QString::fromStdString(it->first))->addEvents(it->second.timeline);
|
models.value(QString::fromStdString(it->first))->addEvents(it->second.timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this->isInitialSync_ = false;
|
||||||
|
emit initialSyncChanged(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -12,10 +12,6 @@
|
||||||
#include "TimelineModel.h"
|
#include "TimelineModel.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
// temporary for stubs
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
|
||||||
|
|
||||||
class MxcImageProvider;
|
class MxcImageProvider;
|
||||||
class ColorImageProvider;
|
class ColorImageProvider;
|
||||||
|
|
||||||
|
@ -25,6 +21,8 @@ class TimelineViewManager : public QObject
|
||||||
|
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
|
TimelineModel *timeline MEMBER timeline_ READ activeTimeline NOTIFY activeTimelineChanged)
|
||||||
|
Q_PROPERTY(
|
||||||
|
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimelineViewManager(QWidget *parent = 0);
|
TimelineViewManager(QWidget *parent = 0);
|
||||||
|
@ -36,6 +34,7 @@ public:
|
||||||
void clearAll() { models.clear(); }
|
void clearAll() { models.clear(); }
|
||||||
|
|
||||||
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
|
||||||
|
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
|
||||||
void openImageOverlay(QString mxcUrl,
|
void openImageOverlay(QString mxcUrl,
|
||||||
QString originalFilename,
|
QString originalFilename,
|
||||||
QString mimeType,
|
QString mimeType,
|
||||||
|
@ -66,6 +65,7 @@ signals:
|
||||||
void clearRoomMessageCount(QString roomid);
|
void clearRoomMessageCount(QString roomid);
|
||||||
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|
||||||
void activeTimelineChanged(TimelineModel *timeline);
|
void activeTimelineChanged(TimelineModel *timeline);
|
||||||
|
void initialSyncChanged(bool isInitialSync);
|
||||||
void mediaCached(QString mxcUrl, QString cacheUrl);
|
void mediaCached(QString mxcUrl, QString cacheUrl);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -107,11 +107,11 @@ private:
|
||||||
QQuickWidget *view;
|
QQuickWidget *view;
|
||||||
#endif
|
#endif
|
||||||
QWidget *container;
|
QWidget *container;
|
||||||
TimelineModel *timeline_ = nullptr;
|
|
||||||
MxcImageProvider *imgProvider;
|
MxcImageProvider *imgProvider;
|
||||||
ColorImageProvider *colorImgProvider;
|
ColorImageProvider *colorImgProvider;
|
||||||
|
|
||||||
QHash<QString, QSharedPointer<TimelineModel>> models;
|
QHash<QString, QSharedPointer<TimelineModel>> models;
|
||||||
|
TimelineModel *timeline_ = nullptr;
|
||||||
|
bool isInitialSync_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
|
|
Loading…
Reference in a new issue