diff --git a/CMakeLists.txt b/CMakeLists.txt
index 699623bd..08d3cf68 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -123,6 +123,8 @@ set(SRC_FILES
src/TimelineView.cc
src/TimelineViewManager.cc
src/InputValidator.cc
+ src/JoinRoomDialog.cc
+ src/LeaveRoomDialog.cc
src/Login.cc
src/LoginPage.cc
src/LogoutDialog.cc
@@ -203,9 +205,11 @@ qt5_wrap_cpp(MOC_HEADERS
include/EmojiPickButton.h
include/ImageItem.h
include/ImageOverlayDialog.h
+ include/JoinRoomDialog.h
include/TimelineItem.h
include/TimelineView.h
include/TimelineViewManager.h
+ include/LeaveRoomDialog.h
include/LoginPage.h
include/LogoutDialog.h
include/MainWindow.h
diff --git a/include/Cache.h b/include/Cache.h
index 1be56620..0f6e5cd2 100644
--- a/include/Cache.h
+++ b/include/Cache.h
@@ -37,6 +37,8 @@ public:
inline void unmount();
inline QString memberDbName(const QString &roomid);
+ void removeRoom(const QString &roomid);
+
private:
void setNextBatchToken(lmdb::txn &txn, const QString &token);
void insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &state);
diff --git a/include/ChatPage.h b/include/ChatPage.h
index 0ea7ea38..8becc17f 100644
--- a/include/ChatPage.h
+++ b/include/ChatPage.h
@@ -61,6 +61,8 @@ private slots:
void changeTopRoomInfo(const QString &room_id);
void startSync();
void logout();
+ void addRoom(const QString &room_id);
+ void removeRoom(const QString &room_id);
protected:
void keyPressEvent(QKeyEvent *event) override;
diff --git a/include/JoinRoomDialog.h b/include/JoinRoomDialog.h
new file mode 100644
index 00000000..6c3fbdcf
--- /dev/null
+++ b/include/JoinRoomDialog.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <QFrame>
+#include <QLineEdit>
+
+#include "FlatButton.h"
+
+class JoinRoomDialog : public QFrame
+{
+ Q_OBJECT
+public:
+ JoinRoomDialog(QWidget *parent = nullptr);
+
+signals:
+ void closing(bool isJoining, QString roomAlias);
+
+private:
+ FlatButton *confirmBtn_;
+ FlatButton *cancelBtn_;
+
+ QLineEdit *roomAliasEdit_;
+};
diff --git a/include/LeaveRoomDialog.h b/include/LeaveRoomDialog.h
new file mode 100644
index 00000000..1639a578
--- /dev/null
+++ b/include/LeaveRoomDialog.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <QFrame>
+
+#include "FlatButton.h"
+
+class LeaveRoomDialog : public QFrame
+{
+ Q_OBJECT
+public:
+ explicit LeaveRoomDialog(QWidget *parent = nullptr);
+
+signals:
+ void closing(bool isLeaving);
+
+private:
+ FlatButton *confirmBtn_;
+ FlatButton *cancelBtn_;
+};
diff --git a/include/MatrixClient.h b/include/MatrixClient.h
index 8d6c60a7..cd023650 100644
--- a/include/MatrixClient.h
+++ b/include/MatrixClient.h
@@ -52,6 +52,8 @@ public:
void downloadImage(const QString &event_id, const QUrl &url);
void messages(const QString &room_id, const QString &from_token, int limit = 20) noexcept;
void uploadImage(const QString &roomid, const QString &filename);
+ void joinRoom(const QString &roomIdOrAlias);
+ void leaveRoom(const QString &roomId);
inline QUrl getHomeServer();
inline int transactionId();
@@ -94,6 +96,8 @@ signals:
void messageSent(const QString &event_id, const QString &roomid, const int txn_id);
void emoteSent(const QString &event_id, const QString &roomid, const int txn_id);
void messagesRetrieved(const QString &room_id, const RoomMessages &msgs);
+ void joinedRoom(const QString &room_id);
+ void leftRoom(const QString &room_id);
private slots:
void onResponse(QNetworkReply *reply);
@@ -115,6 +119,8 @@ private:
Sync,
UserAvatar,
Versions,
+ JoinRoom,
+ LeaveRoom,
};
// Response handlers.
@@ -132,6 +138,8 @@ private:
void onSyncResponse(QNetworkReply *reply);
void onUserAvatarResponse(QNetworkReply *reply);
void onVersionsResponse(QNetworkReply *reply);
+ void onJoinRoomResponse(QNetworkReply *reply);
+ void onLeaveRoomResponse(QNetworkReply *reply);
// Client API prefix.
QString clientApiUrl_;
diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h
index 8947ee8e..03023038 100644
--- a/include/RoomInfoListItem.h
+++ b/include/RoomInfoListItem.h
@@ -57,6 +57,7 @@ public:
signals:
void clicked(const QString &room_id);
+ void leaveRoom(const QString &room_id);
public slots:
void setPressedState(bool state);
@@ -86,6 +87,7 @@ private:
Menu *menu_;
QAction *toggleNotifications_;
+ QAction *leaveRoom_;
QSharedPointer<RoomSettings> roomSettings_;
diff --git a/include/RoomList.h b/include/RoomList.h
index 573f43a8..c2f4255d 100644
--- a/include/RoomList.h
+++ b/include/RoomList.h
@@ -17,12 +17,16 @@
#pragma once
+#include <QPushButton>
#include <QScrollArea>
#include <QSharedPointer>
#include <QVBoxLayout>
#include <QWidget>
+#include "JoinRoomDialog.h"
+#include "LeaveRoomDialog.h"
#include "MatrixClient.h"
+#include "OverlayModal.h"
#include "RoomInfoListItem.h"
#include "RoomState.h"
#include "Sync.h"
@@ -41,6 +45,11 @@ public:
void clear();
+ void addRoom(const QSharedPointer<RoomSettings> &settings,
+ const RoomState &state,
+ const QString &room_id);
+ void removeRoom(const QString &room_id, bool reset);
+
signals:
void roomChanged(const QString &room_id);
void totalUnreadMessageCountUpdated(int count);
@@ -50,6 +59,9 @@ public slots:
void highlightSelectedRoom(const QString &room_id);
void updateUnreadMessageCount(const QString &roomid, int count);
void updateRoomDescription(const QString &roomid, const DescInfo &info);
+ void closeJoinRoomDialog(bool isJoining, QString roomAlias);
+ void openLeaveRoomDialog(const QString &room_id);
+ void closeLeaveRoomDialog(bool leaving, const QString &room_id);
private:
void calculateUnreadMessageCount();
@@ -59,6 +71,14 @@ private:
QScrollArea *scrollArea_;
QWidget *scrollAreaContents_;
+ QPushButton *joinRoomButton_;
+
+ OverlayModal *joinRoomModal_;
+ JoinRoomDialog *joinRoomDialog_;
+
+ OverlayModal *leaveRoomModal;
+ LeaveRoomDialog *leaveRoomDialog_;
+
QMap<QString, QSharedPointer<RoomInfoListItem>> rooms_;
QSharedPointer<MatrixClient> client_;
diff --git a/include/Sync.h b/include/Sync.h
index 420c9b6c..a9caf473 100644
--- a/include/Sync.h
+++ b/include/Sync.h
@@ -171,15 +171,42 @@ JoinedRoom::timeline() const
return timeline_;
}
+class LeftRoom : public Deserializable
+{
+public:
+ inline State state() const;
+ inline Timeline timeline() const;
+
+ void deserialize(const QJsonValue &data) override;
+
+private:
+ State state_;
+ Timeline timeline_;
+};
+
+inline State
+LeftRoom::state() const
+{
+ return state_;
+}
+
+inline Timeline
+LeftRoom::timeline() const
+{
+ return timeline_;
+}
+
// TODO: Add support for invited and left rooms.
class Rooms : public Deserializable
{
public:
inline QMap<QString, JoinedRoom> join() const;
+ inline QMap<QString, LeftRoom> leave() const;
void deserialize(const QJsonValue &data) override;
private:
QMap<QString, JoinedRoom> join_;
+ QMap<QString, LeftRoom> leave_;
};
inline QMap<QString, JoinedRoom>
@@ -188,6 +215,12 @@ Rooms::join() const
return join_;
}
+inline QMap<QString, LeftRoom>
+Rooms::leave() const
+{
+ return leave_;
+}
+
class SyncResponse : public Deserializable
{
public:
diff --git a/include/TimelineViewManager.h b/include/TimelineViewManager.h
index 14f47928..35dcac5a 100644
--- a/include/TimelineViewManager.h
+++ b/include/TimelineViewManager.h
@@ -40,6 +40,10 @@ public:
void initialize(const Rooms &rooms);
// Empty initialization.
void initialize(const QList<QString> &rooms);
+
+ void addRoom(const JoinedRoom &room, const QString &room_id);
+ void addRoom(const QString &room_id);
+
void sync(const Rooms &rooms);
void clearAll();
diff --git a/include/TopRoomBar.h b/include/TopRoomBar.h
index 6b24cbef..5d8b394e 100644
--- a/include/TopRoomBar.h
+++ b/include/TopRoomBar.h
@@ -29,7 +29,9 @@
#include "Avatar.h"
#include "FlatButton.h"
+#include "LeaveRoomDialog.h"
#include "Menu.h"
+#include "OverlayModal.h"
#include "RoomSettings.h"
static const QString URL_HTML = "<a href=\"\\1\" style=\"color: #333333\">\\1</a>";
@@ -51,9 +53,15 @@ public:
void reset();
+signals:
+ void leaveRoom();
+
protected:
void paintEvent(QPaintEvent *event) override;
+private slots:
+ void closeLeaveRoomDialog(bool leaving);
+
private:
QHBoxLayout *topLayout_;
QVBoxLayout *textLayout_;
@@ -65,9 +73,13 @@ private:
QMenu *menu_;
QAction *toggleNotifications_;
+ QAction *leaveRoom_;
FlatButton *settingsBtn_;
+ OverlayModal *leaveRoomModal;
+ LeaveRoomDialog *leaveRoomDialog_;
+
Avatar *avatar_;
int buttonSize_;
diff --git a/src/Cache.cc b/src/Cache.cc
index bda81316..5ed77086 100644
--- a/src/Cache.cc
+++ b/src/Cache.cc
@@ -153,6 +153,16 @@ Cache::insertRoomState(lmdb::txn &txn, const QString &roomid, const RoomState &s
}
}
+void
+Cache::removeRoom(const QString &roomid)
+{
+ auto txn = lmdb::txn::begin(env_, nullptr, 0);
+
+ lmdb::dbi_del(txn, roomDb_, lmdb::val(roomid.toUtf8(), roomid.toUtf8().size()), nullptr);
+
+ txn.commit();
+}
+
QMap<QString, RoomState>
Cache::states()
{
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 3d3a3876..a6a80e9d 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -114,6 +114,9 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect(user_info_widget_, SIGNAL(logout()), client_.data(), SLOT(logout()));
connect(client_.data(), SIGNAL(loggedOut()), this, SLOT(logout()));
+ connect(
+ top_bar_, &TopRoomBar::leaveRoom, this, [=]() { client_->leaveRoom(current_room_); });
+
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, text_input_, &TextInputWidget::focusLineEdit);
connect(
@@ -190,6 +193,14 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
SIGNAL(ownAvatarRetrieved(const QPixmap &)),
this,
SLOT(setOwnAvatar(const QPixmap &)));
+ connect(client_.data(),
+ SIGNAL(joinedRoom(const QString &)),
+ this,
+ SLOT(addRoom(const QString &)));
+ connect(client_.data(),
+ SIGNAL(leftRoom(const QString &)),
+ this,
+ SLOT(removeRoom(const QString &)));
AvatarProvider::init(client);
}
@@ -293,8 +304,9 @@ ChatPage::syncCompleted(const SyncResponse &response)
RoomState room_state;
// Merge the new updates for rooms that we are tracking.
- if (state_manager_.contains(it.key()))
+ if (state_manager_.contains(it.key())) {
room_state = state_manager_[it.key()];
+ }
room_state.updateFromEvents(it.value().state().events());
room_state.updateFromEvents(it.value().timeline().events());
@@ -307,13 +319,48 @@ ChatPage::syncCompleted(const SyncResponse &response)
oldState.update(room_state);
state_manager_.insert(it.key(), oldState);
} else {
- qWarning() << "New rooms cannot be added after initial sync, yet.";
+ RoomState room_state;
+
+ // Build the current state from the timeline and state events.
+ room_state.updateFromEvents(it.value().state().events());
+ room_state.updateFromEvents(it.value().timeline().events());
+
+ // Remove redundant memberships.
+ room_state.removeLeaveMemberships();
+
+ // Resolve room name and avatar. e.g in case of one-to-one chats.
+ room_state.resolveName();
+ room_state.resolveAvatar();
+
+ updateDisplayNames(room_state);
+
+ state_manager_.insert(it.key(), room_state);
+ settingsManager_.insert(
+ it.key(), QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
+
+ for (const auto membership : room_state.memberships) {
+ auto uid = membership.sender();
+ auto url = membership.content().avatarUrl();
+
+ if (!url.toString().isEmpty())
+ AvatarProvider::setAvatarUrl(uid, url);
+ }
+
+ view_manager_->addRoom(it.value(), it.key());
}
if (it.key() == current_room_)
changeTopRoomInfo(it.key());
}
+ auto leave = response.rooms().leave();
+
+ for (auto it = leave.constBegin(); it != leave.constEnd(); it++) {
+ if (state_manager_.contains(it.key())) {
+ removeRoom(it.key());
+ }
+ }
+
try {
cache_->setState(response.nextBatch(), state_manager_);
} catch (const lmdb::error &e) {
@@ -537,6 +584,38 @@ ChatPage::showQuickSwitcher()
quickSwitcherModal_->fadeIn();
}
+void
+ChatPage::addRoom(const QString &room_id)
+{
+ if (!state_manager_.contains(room_id)) {
+ RoomState room_state;
+
+ state_manager_.insert(room_id, room_state);
+ settingsManager_.insert(room_id,
+ QSharedPointer<RoomSettings>(new RoomSettings(room_id)));
+
+ room_list_->addRoom(settingsManager_[room_id], state_manager_[room_id], room_id);
+
+ this->changeTopRoomInfo(room_id);
+ room_list_->highlightSelectedRoom(room_id);
+ }
+}
+
+void
+ChatPage::removeRoom(const QString &room_id)
+{
+ state_manager_.remove(room_id);
+ settingsManager_.remove(room_id);
+ try {
+ cache_->removeRoom(room_id);
+ } catch (const lmdb::error &e) {
+ qCritical() << "The cache couldn't be updated: " << e.what();
+ // TODO: Notify the user.
+ cache_->unmount();
+ }
+ room_list_->removeRoom(room_id, room_id == current_room_);
+}
+
ChatPage::~ChatPage()
{
sync_timer_->stop();
diff --git a/src/EmojiPanel.cc b/src/EmojiPanel.cc
index c272a478..2730ddb5 100644
--- a/src/EmojiPanel.cc
+++ b/src/EmojiPanel.cc
@@ -34,12 +34,13 @@ EmojiPanel::EmojiPanel(QWidget *parent)
, animationDuration_{ 100 }
, categoryIconSize_{ 20 }
{
- setStyleSheet(
- "QWidget {background: #fff; color: #e8e8e8; border: none;}"
- "QScrollBar:vertical { background-color: #fff; width: 8px; margin: 0px 2px 0 2px; }"
- "QScrollBar::handle:vertical { background-color: #d6dde3; min-height: 20px; }"
- "QScrollBar::add-line:vertical { border: none; background: none; }"
- "QScrollBar::sub-line:vertical { border: none; background: none; }");
+ setStyleSheet("QWidget {background: #fff; color: #e8e8e8; border: none;}"
+ "QScrollBar:vertical { background-color: #fff; width: 8px; margin: 0px "
+ "2px 0 2px; }"
+ "QScrollBar::handle:vertical { background-color: #d6dde3; min-height: "
+ "20px; }"
+ "QScrollBar::add-line:vertical { border: none; background: none; }"
+ "QScrollBar::sub-line:vertical { border: none; background: none; }");
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_ShowWithoutActivating, true);
diff --git a/src/InputValidator.cc b/src/InputValidator.cc
index 6e343c71..5fd92783 100644
--- a/src/InputValidator.cc
+++ b/src/InputValidator.cc
@@ -20,8 +20,8 @@
const QRegExp MXID_REGEX("@[A-Za-z0-9._%+-]+:[A-Za-z0-9.-]{1,126}\\.[A-Za-z]{1,63}");
const QRegExp LOCALPART_REGEX("[A-za-z0-9._%+-]{3,}");
const QRegExp PASSWORD_REGEX(".{8,}");
-const QRegExp DOMAIN_REGEX(
- "(?!\\-)(?:[a-zA-Z\\d\\-]{0,62}[a-zA-Z\\d]\\.){1,126}(?!\\d+)[a-zA-Z\\d]{1,63}");
+const QRegExp DOMAIN_REGEX("(?!\\-)(?:[a-zA-Z\\d\\-]{0,62}[a-zA-Z\\d]\\.){1,"
+ "126}(?!\\d+)[a-zA-Z\\d]{1,63}");
QRegExpValidator InputValidator::Id(MXID_REGEX);
QRegExpValidator InputValidator::Localpart(LOCALPART_REGEX);
diff --git a/src/JoinRoomDialog.cc b/src/JoinRoomDialog.cc
new file mode 100644
index 00000000..c3ee289e
--- /dev/null
+++ b/src/JoinRoomDialog.cc
@@ -0,0 +1,49 @@
+#include <QLabel>
+#include <QVBoxLayout>
+
+#include "Config.h"
+#include "JoinRoomDialog.h"
+#include "Theme.h"
+
+JoinRoomDialog::JoinRoomDialog(QWidget *parent)
+ : QFrame(parent)
+{
+ setMaximumSize(400, 400);
+ setStyleSheet("background-color: #fff");
+
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(30);
+ layout->setMargin(20);
+
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(0);
+ buttonLayout->setMargin(0);
+
+ confirmBtn_ = new FlatButton("JOIN", this);
+ confirmBtn_->setFontSize(conf::btn::fontSize);
+
+ cancelBtn_ = new FlatButton(tr("CANCEL"), this);
+ cancelBtn_->setFontSize(conf::btn::fontSize);
+
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addWidget(cancelBtn_);
+
+ QFont font;
+ font.setPixelSize(conf::headerFontSize);
+
+ auto label = new QLabel(tr("Room alias to join:"), this);
+ label->setFont(font);
+ label->setStyleSheet("color: #333333");
+
+ roomAliasEdit_ = new QLineEdit(this);
+
+ layout->addWidget(label);
+ layout->addWidget(roomAliasEdit_);
+ layout->addLayout(buttonLayout);
+
+ connect(confirmBtn_, &QPushButton::clicked, [=]() {
+ emit closing(true, roomAliasEdit_->text());
+ });
+ connect(cancelBtn_, &QPushButton::clicked, [=]() { emit closing(false, nullptr); });
+}
diff --git a/src/LeaveRoomDialog.cc b/src/LeaveRoomDialog.cc
new file mode 100644
index 00000000..f7669f0d
--- /dev/null
+++ b/src/LeaveRoomDialog.cc
@@ -0,0 +1,44 @@
+#include <QLabel>
+#include <QVBoxLayout>
+
+#include "Config.h"
+#include "LeaveRoomDialog.h"
+#include "Theme.h"
+
+LeaveRoomDialog::LeaveRoomDialog(QWidget *parent)
+ : QFrame(parent)
+{
+ setMaximumSize(400, 400);
+ setStyleSheet("background-color: #fff");
+
+ auto layout = new QVBoxLayout(this);
+ layout->setSpacing(30);
+ layout->setMargin(20);
+
+ auto buttonLayout = new QHBoxLayout();
+ buttonLayout->setSpacing(0);
+ buttonLayout->setMargin(0);
+
+ confirmBtn_ = new FlatButton("LEAVE", this);
+ confirmBtn_->setFontSize(conf::btn::fontSize);
+
+ cancelBtn_ = new FlatButton(tr("CANCEL"), this);
+ cancelBtn_->setFontSize(conf::btn::fontSize);
+
+ buttonLayout->addStretch(1);
+ buttonLayout->addWidget(confirmBtn_);
+ buttonLayout->addWidget(cancelBtn_);
+
+ QFont font;
+ font.setPixelSize(conf::headerFontSize);
+
+ auto label = new QLabel(tr("Are you sure you want to leave?"), this);
+ label->setFont(font);
+ label->setStyleSheet("color: #333333");
+
+ layout->addWidget(label);
+ layout->addLayout(buttonLayout);
+
+ connect(confirmBtn_, &QPushButton::clicked, [=]() { emit closing(true); });
+ connect(cancelBtn_, &QPushButton::clicked, [=]() { emit closing(false); });
+}
diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc
index 981a30c2..bd43efd8 100644
--- a/src/MatrixClient.cc
+++ b/src/MatrixClient.cc
@@ -463,6 +463,40 @@ MatrixClient::onMessagesResponse(QNetworkReply *reply)
}
void
+MatrixClient::onJoinRoomResponse(QNetworkReply *reply)
+{
+ reply->deleteLater();
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ if (status == 0 || status >= 400) {
+ qWarning() << reply->errorString();
+ return;
+ }
+
+ auto data = reply->readAll();
+ QJsonDocument response = QJsonDocument::fromJson(data);
+ QString room_id = response.object()["room_id"].toString();
+ emit joinedRoom(room_id);
+}
+
+void
+MatrixClient::onLeaveRoomResponse(QNetworkReply *reply)
+{
+ reply->deleteLater();
+
+ int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+
+ if (status == 0 || status >= 400) {
+ qWarning() << reply->errorString();
+ return;
+ }
+
+ QString room_id = reply->property("room_id").toString();
+ emit leftRoom(room_id);
+}
+
+void
MatrixClient::onResponse(QNetworkReply *reply)
{
switch (static_cast<Endpoint>(reply->property("endpoint").toInt())) {
@@ -508,6 +542,12 @@ MatrixClient::onResponse(QNetworkReply *reply)
case Endpoint::Messages:
onMessagesResponse(reply);
break;
+ case Endpoint::JoinRoom:
+ onJoinRoomResponse(reply);
+ break;
+ case Endpoint::LeaveRoom:
+ onLeaveRoomResponse(reply);
+ break;
default:
break;
}
@@ -571,7 +611,8 @@ void
MatrixClient::sync() noexcept
{
QJsonObject filter{ { "room",
- QJsonObject{ { "ephemeral", QJsonObject{ { "limit", 0 } } } } },
+ QJsonObject{ { "include_leave", true },
+ { "ephemeral", QJsonObject{ { "limit", 0 } } } } },
{ "presence", QJsonObject{ { "limit", 0 } } } };
QUrlQuery query;
@@ -842,3 +883,38 @@ MatrixClient::uploadImage(const QString &roomid, const QString &filename)
reply->setProperty("room_id", roomid);
reply->setProperty("filename", filename);
}
+
+void
+MatrixClient::joinRoom(const QString &roomIdOrAlias)
+{
+ QUrlQuery query;
+ query.addQueryItem("access_token", token_);
+
+ QUrl endpoint(server_);
+ endpoint.setPath(clientApiUrl_ + QString("/join/%1").arg(roomIdOrAlias));
+ endpoint.setQuery(query);
+
+ QNetworkRequest request(endpoint);
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+
+ QNetworkReply *reply = post(request, "{}");
+ reply->setProperty("endpoint", static_cast<int>(Endpoint::JoinRoom));
+}
+
+void
+MatrixClient::leaveRoom(const QString &roomId)
+{
+ QUrlQuery query;
+ query.addQueryItem("access_token", token_);
+
+ QUrl endpoint(server_);
+ endpoint.setPath(clientApiUrl_ + QString("/rooms/%1/leave").arg(roomId));
+ endpoint.setQuery(query);
+
+ QNetworkRequest request(endpoint);
+ request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
+
+ QNetworkReply *reply = post(request, "{}");
+ reply->setProperty("room_id", roomId);
+ reply->setProperty("endpoint", static_cast<int>(Endpoint::LeaveRoom));
+}
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc
index 533fc267..cd15d839 100644
--- a/src/RoomInfoListItem.cc
+++ b/src/RoomInfoListItem.cc
@@ -53,12 +53,15 @@ RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
menu_ = new Menu(this);
toggleNotifications_ = new QAction(notificationText(), this);
-
connect(toggleNotifications_, &QAction::triggered, this, [=]() {
roomSettings_->toggleNotifications();
});
+ leaveRoom_ = new QAction(tr("Leave room"), this);
+ connect(leaveRoom_, &QAction::triggered, this, [=]() { emit leaveRoom(room_id); });
+
menu_->addAction(toggleNotifications_);
+ menu_->addAction(leaveRoom_);
}
QString
diff --git a/src/RoomList.cc b/src/RoomList.cc
index 5c67d98f..9dc7e1c2 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -19,6 +19,7 @@
#include <QJsonArray>
#include <QRegularExpression>
+#include "MainWindow.h"
#include "RoomInfoListItem.h"
#include "RoomList.h"
#include "Sync.h"
@@ -70,6 +71,36 @@ RoomList::clear()
}
void
+RoomList::addRoom(const QSharedPointer<RoomSettings> &settings,
+ const RoomState &state,
+ const QString &room_id)
+{
+ RoomInfoListItem *room_item = new RoomInfoListItem(settings, state, room_id, scrollArea_);
+ connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
+ connect(room_item, &RoomInfoListItem::leaveRoom, this, &RoomList::openLeaveRoomDialog);
+
+ rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));
+
+ client_->fetchRoomAvatar(room_id, state.getAvatar());
+
+ contentsLayout_->insertWidget(0, room_item);
+}
+
+void
+RoomList::removeRoom(const QString &room_id, bool reset)
+{
+ rooms_.remove(room_id);
+
+ if (rooms_.isEmpty() || !reset)
+ return;
+
+ auto first_room = rooms_.first();
+ first_room->setPressedState(true);
+
+ emit roomChanged(rooms_.firstKey());
+}
+
+void
RoomList::updateUnreadMessageCount(const QString &roomid, int count)
{
if (!rooms_.contains(roomid)) {
@@ -116,6 +147,7 @@ RoomList::setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &set
new RoomInfoListItem(settings[room_id], state, room_id, scrollArea_);
connect(
room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
+ connect(room_item, &RoomInfoListItem::leaveRoom, this, &RoomList::openLeaveRoomDialog);
rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));
@@ -133,15 +165,31 @@ RoomList::setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &set
}
void
+RoomList::openLeaveRoomDialog(const QString &room_id)
+{
+ leaveRoomDialog_ = new LeaveRoomDialog(this);
+ connect(leaveRoomDialog_,
+ &LeaveRoomDialog::closing, this,
+ [=](bool leaving) { closeLeaveRoomDialog(leaving, room_id); });
+
+ leaveRoomModal = new OverlayModal(MainWindow::instance(), leaveRoomDialog_);
+ leaveRoomModal->setDuration(0);
+ leaveRoomModal->setColor(QColor(55, 55, 55, 170));
+
+ leaveRoomModal->fadeIn();
+}
+
+void
RoomList::sync(const QMap<QString, RoomState> &states)
{
for (auto it = states.constBegin(); it != states.constEnd(); it++) {
auto room_id = it.key();
auto state = it.value();
- // TODO: Add the new room to the list.
- if (!rooms_.contains(room_id))
- continue;
+ if (!rooms_.contains(room_id)) {
+ addRoom(
+ QSharedPointer<RoomSettings>(new RoomSettings(room_id)), state, room_id);
+ }
auto room = rooms_[room_id];
@@ -203,3 +251,23 @@ RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
rooms_.value(roomid)->setDescriptionMessage(info);
}
+
+void
+RoomList::closeJoinRoomDialog(bool isJoining, QString roomAlias)
+{
+ joinRoomModal_->fadeOut();
+
+ if (isJoining) {
+ client_->joinRoom(roomAlias);
+ }
+}
+
+void
+RoomList::closeLeaveRoomDialog(bool leaving, const QString &room_id)
+{
+ leaveRoomModal->fadeOut();
+
+ if (leaving) {
+ client_->leaveRoom(room_id);
+ }
+}
diff --git a/src/Sync.cc b/src/Sync.cc
index 58c423d1..90314352 100644
--- a/src/Sync.cc
+++ b/src/Sync.cc
@@ -90,7 +90,6 @@ Rooms::deserialize(const QJsonValue &data)
for (auto it = join.constBegin(); it != join.constEnd(); it++) {
JoinedRoom tmp_room;
-
try {
tmp_room.deserialize(it.value());
join_.insert(it.key(), tmp_room);
@@ -112,7 +111,19 @@ Rooms::deserialize(const QJsonValue &data)
if (!object.value("leave").isObject()) {
throw DeserializationException("rooms/leave must be a JSON object");
}
- // TODO: Implement leave handling
+ auto leave = object.value("leave").toObject();
+
+ for (auto it = leave.constBegin(); it != leave.constEnd(); it++) {
+ LeftRoom tmp_room;
+
+ try {
+ tmp_room.deserialize(it.value());
+ leave_.insert(it.key(), tmp_room);
+ } catch (DeserializationException &e) {
+ qWarning() << e.what();
+ qWarning() << "Skipping malformed object for room" << it.key();
+ }
+ }
}
}
@@ -185,6 +196,32 @@ JoinedRoom::deserialize(const QJsonValue &data)
}
void
+LeftRoom::deserialize(const QJsonValue &data)
+{
+ if (!data.isObject())
+ throw DeserializationException("LeftRoom is not a JSON object");
+
+ QJsonObject object = data.toObject();
+
+ if (!object.contains("state"))
+ throw DeserializationException("leave/state is missing");
+
+ if (!object.contains("timeline"))
+ throw DeserializationException("leave/timeline is missing");
+
+ if (!object.value("state").isObject())
+ throw DeserializationException("leave/state should be an object");
+
+ QJsonObject state = object.value("state").toObject();
+
+ if (!state.contains("events"))
+ throw DeserializationException("leave/state/events is missing");
+
+ state_.deserialize(state.value("events"));
+ timeline_.deserialize(object.value("timeline"));
+}
+
+void
Event::deserialize(const QJsonValue &data)
{
if (!data.isObject())
diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc
index a4d616c3..1969ae5b 100644
--- a/src/TimelineViewManager.cc
+++ b/src/TimelineViewManager.cc
@@ -101,19 +101,7 @@ void
TimelineViewManager::initialize(const Rooms &rooms)
{
for (auto it = rooms.join().constBegin(); it != rooms.join().constEnd(); it++) {
- auto roomid = it.key();
-
- // Create a history view with the room events.
- TimelineView *view = new TimelineView(it.value().timeline(), client_, it.key());
- views_.insert(it.key(), QSharedPointer<TimelineView>(view));
-
- connect(view,
- &TimelineView::updateLastTimelineMessage,
- this,
- &TimelineViewManager::updateRoomsLastMessage);
-
- // Add the view in the widget stack.
- addWidget(view);
+ addRoom(it.value(), it.key());
}
}
@@ -121,18 +109,40 @@ void
TimelineViewManager::initialize(const QList<QString> &rooms)
{
for (const auto &roomid : rooms) {
- // Create a history view without any events.
- TimelineView *view = new TimelineView(client_, roomid);
- views_.insert(roomid, QSharedPointer<TimelineView>(view));
+ addRoom(roomid);
+ }
+}
+
+void
+TimelineViewManager::addRoom(const JoinedRoom &room, const QString &room_id)
+{
+ // Create a history view with the room events.
+ TimelineView *view = new TimelineView(room.timeline(), client_, room_id);
+ views_.insert(room_id, QSharedPointer<TimelineView>(view));
- connect(view,
- &TimelineView::updateLastTimelineMessage,
- this,
- &TimelineViewManager::updateRoomsLastMessage);
+ connect(view,
+ &TimelineView::updateLastTimelineMessage,
+ this,
+ &TimelineViewManager::updateRoomsLastMessage);
- // Add the view in the widget stack.
- addWidget(view);
- }
+ // Add the view in the widget stack.
+ addWidget(view);
+}
+
+void
+TimelineViewManager::addRoom(const QString &room_id)
+{
+ // Create a history view without any events.
+ TimelineView *view = new TimelineView(client_, room_id);
+ views_.insert(room_id, QSharedPointer<TimelineView>(view));
+
+ connect(view,
+ &TimelineView::updateLastTimelineMessage,
+ this,
+ &TimelineViewManager::updateRoomsLastMessage);
+
+ // Add the view in the widget stack.
+ addWidget(view);
}
void
diff --git a/src/TopRoomBar.cc b/src/TopRoomBar.cc
index 1805f063..f8a7e600 100644
--- a/src/TopRoomBar.cc
+++ b/src/TopRoomBar.cc
@@ -18,6 +18,7 @@
#include <QStyleOption>
#include "Config.h"
+#include "MainWindow.h"
#include "TopRoomBar.h"
TopRoomBar::TopRoomBar(QWidget *parent)
@@ -83,7 +84,21 @@ TopRoomBar::TopRoomBar(QWidget *parent)
roomSettings_->toggleNotifications();
});
+ leaveRoom_ = new QAction(tr("Leave room"), this);
+ connect(leaveRoom_, &QAction::triggered, this, [=]() {
+ leaveRoomDialog_ = new LeaveRoomDialog(this);
+ connect(
+ leaveRoomDialog_, SIGNAL(closing(bool)), this, SLOT(closeLeaveRoomDialog(bool)));
+
+ leaveRoomModal = new OverlayModal(MainWindow::instance(), leaveRoomDialog_);
+ leaveRoomModal->setDuration(100);
+ leaveRoomModal->setColor(QColor(55, 55, 55, 170));
+
+ leaveRoomModal->fadeIn();
+ });
+
menu_->addAction(toggleNotifications_);
+ menu_->addAction(leaveRoom_);
connect(settingsBtn_, &QPushButton::clicked, this, [=]() {
if (roomSettings_->isNotificationsEnabled())
@@ -100,6 +115,16 @@ TopRoomBar::TopRoomBar(QWidget *parent)
}
void
+TopRoomBar::closeLeaveRoomDialog(bool leaving)
+{
+ leaveRoomModal->fadeOut();
+
+ if (leaving) {
+ emit leaveRoom();
+ }
+}
+
+void
TopRoomBar::updateRoomAvatarFromName(const QString &name)
{
QChar letter = '?';
|