diff --git a/CMakeLists.txt b/CMakeLists.txt index c15093ad..f34d2ce6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -128,6 +128,9 @@ set(SRC_FILES src/AvatarProvider.cc src/Cache.cc src/ChatPage.cc + src/CommunitiesListItem.cc + src/CommunitiesList.cc + src/Community.cc src/Deserializable.cc src/EmojiCategory.cc src/EmojiItemDelegate.cc @@ -221,6 +224,9 @@ include_directories(${LMDB_INCLUDE_DIR}) qt5_wrap_cpp(MOC_HEADERS include/AvatarProvider.h include/ChatPage.h + include/CommunitiesListItem.h + include/CommunitiesList.h + include/Community.h include/EmojiCategory.h include/EmojiItemDelegate.h include/EmojiPanel.h diff --git a/include/ChatPage.h b/include/ChatPage.h index 416f7870..567ef551 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -72,6 +72,7 @@ private slots: void showUnreadMessageNotification(int count); void updateTopBarAvatar(const QString &roomid, const QPixmap &img); void updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_name); + void updateOwnCommunitiesInfo(const QList &own_communities); void setOwnAvatar(const QPixmap &img); void initialSyncCompleted(const SyncResponse &response); void syncCompleted(const SyncResponse &response); @@ -108,6 +109,8 @@ private: Splitter *splitter; QWidget *sideBar_; + QWidget *communitiesSideBar_; + QVBoxLayout *communitiesSideBarLayout_; QVBoxLayout *sideBarLayout_; QVBoxLayout *sideBarTopLayout_; QVBoxLayout *sideBarMainLayout_; @@ -119,7 +122,9 @@ private: QHBoxLayout *topBarLayout_; QVBoxLayout *mainContentLayout_; + CommunitiesList *communitiesList_; RoomList *room_list_; + TimelineViewManager *view_manager_; SideBarActions *sidebarActions_; @@ -132,13 +137,18 @@ private: QTimer *consensusTimer_; QString current_room_; + QString current_community_; + QMap room_avatars_; + QMap community_avatars_; UserInfoWidget *user_info_widget_; QMap state_manager_; QMap> settingsManager_; + QMap> communityManager_; + // Keeps track of the users currently typing on each room. QMap> typingUsers_; diff --git a/include/CommunitiesList.h b/include/CommunitiesList.h new file mode 100644 index 00000000..ffa3cfcf --- /dev/null +++ b/include/CommunitiesList.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include "MatrixClient.h" +#include "CommunitiesListItem.h" +#include "Community.h" +#include "ui/Theme.h" + +class CommunitiesList : public QWidget +{ + Q_OBJECT + +public: + CommunitiesList(QSharedPointer client, QWidget *parent = nullptr); + ~CommunitiesList(); + + void setCommunities(const QMap> &communities); + void clear(); + + void addCommunity(QSharedPointer community, + const QString &community_id); + void removeCommunity(const QString &community_id); +signals: + void communityChanged(const QString &community_id); + +public slots: + void updateCommunityAvatar(const QString &community_id, + const QPixmap &img); + void highlightSelectedCommunity(const QString &community_id); + +private: + QVBoxLayout *topLayout_; + QVBoxLayout *contentsLayout_; + QWidget *scrollAreaContents_; + QScrollArea *scrollArea_; + + QMap> communities_; + + QSharedPointer client_; +}; diff --git a/include/CommunitiesListItem.h b/include/CommunitiesListItem.h new file mode 100644 index 00000000..52b3e849 --- /dev/null +++ b/include/CommunitiesListItem.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "ui/Theme.h" +#include "Menu.h" +#include "Community.h" + +class CommunitiesListItem : public QWidget +{ + Q_OBJECT + +public: + CommunitiesListItem(QSharedPointer community, + QString community_id, + QWidget *parent = nullptr); + + ~CommunitiesListItem(); + + void setCommunity(QSharedPointer community); + + inline bool isPressed() const; + inline void setAvatar(const QImage &avatar_image); + +signals: + void clicked(const QString &community_id); + +public slots: + void setPressedState(bool state); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + +private: + const int IconSize = 55; + + QSharedPointer community_; + QString communityId_; + QString communityName_; + QString communityShortDescription; + + QPixmap communityAvatar_; + + Menu *menu_; + bool isPressed_ = false; +}; + +inline bool +CommunitiesListItem::isPressed() const +{ + return isPressed_; +} + +inline void +CommunitiesListItem::setAvatar(const QImage &avatar_image) +{ + communityAvatar_ = QPixmap::fromImage( + avatar_image.scaled(IconSize, + IconSize, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation)); + update(); +} + +class WorldCommunityListItem : public CommunitiesListItem +{ + Q_OBJECT +public: + WorldCommunityListItem(QWidget *parent = nullptr); + ~WorldCommunityListItem(); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *event) override; +private: + const int IconSize = 55; +}; diff --git a/include/Community.h b/include/Community.h new file mode 100644 index 00000000..2dccbbc8 --- /dev/null +++ b/include/Community.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +class Community : public QObject +{ + Q_OBJECT + +public: + void parseProfile(const QJsonObject &profile); + void parseRooms(const QJsonObject &rooms); + + inline QUrl getAvatar() const; + inline QString getName() const; + inline QString getShortDescription() const; + inline QString getLongDescription() const; + inline const QList getRoomList() const; + +signals: + void roomsChanged(QList &rooms); + +private: + QUrl avatar_; + QString name_; + QString short_description_; + QString long_description_; + + QList rooms_; +}; + +inline QUrl +Community::getAvatar() const +{ + return avatar_; +} + +inline QString +Community::getName() const +{ + return name_; +} + +inline QString +Community::getShortDescription() const +{ + return short_description_; +} + +inline QString +Community::getLongDescription() const +{ + return long_description_; +} + + +inline const QList +Community::getRoomList() const +{ + return rooms_; +} diff --git a/include/MatrixClient.h b/include/MatrixClient.h index ef9e82e6..a10b1f7b 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -50,6 +50,9 @@ public: void versions() noexcept; void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url); void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl); + void fetchCommunityAvatar(const QString &communityId, const QUrl &avatarUrl); + void fetchCommunityProfile(const QString &communityId); + void fetchCommunityRooms(const QString &communityId); void fetchOwnAvatar(const QUrl &avatar_url); void downloadImage(const QString &event_id, const QUrl &url); void messages(const QString &room_id, const QString &from_token, int limit = 30) noexcept; @@ -65,6 +68,7 @@ public: public slots: void getOwnProfile() noexcept; + void getOwnCommunities() noexcept; void logout() noexcept; void setServer(const QString &server) @@ -90,11 +94,15 @@ signals: void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); void userAvatarRetrieved(const QString &userId, const QImage &img); + void communityAvatarRetrieved(const QString &communityId, const QPixmap &img); + void communityProfileRetrieved(const QString &communityId, const QJsonObject &profile); + void communityRoomsRetrieved(const QString &communityId, const QJsonObject &rooms); void ownAvatarRetrieved(const QPixmap &img); void imageDownloaded(const QString &event_id, const QPixmap &img); // Returned profile data for the user's account. void getOwnProfileResponse(const QUrl &avatar_url, const QString &display_name); + void getOwnCommunitiesResponse(const QList &own_communities); void initialSyncCompleted(const SyncResponse &response); void initialSyncFailed(const QString &msg); void syncCompleted(const SyncResponse &response); diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h index a137b37f..63a73a04 100644 --- a/include/RoomInfoListItem.h +++ b/include/RoomInfoListItem.h @@ -57,6 +57,7 @@ public: void setAvatar(const QImage &avatar_image); void setDescriptionMessage(const DescInfo &info); + inline QString roomId(); signals: void clicked(const QString &room_id); diff --git a/include/RoomList.h b/include/RoomList.h index df668ac6..9e58b64b 100644 --- a/include/RoomList.h +++ b/include/RoomList.h @@ -52,6 +52,8 @@ public: const RoomState &state, const QString &room_id); void removeRoom(const QString &room_id, bool reset); + void setFilterRooms(bool filterRooms); + void setRoomFilter(QList room_ids); signals: void roomChanged(const QString &room_id); @@ -83,6 +85,10 @@ private: QSharedPointer leaveRoomDialog_; QMap> rooms_; + QString selectedRoom_; + + bool filterRooms_ = false; + QList roomFilter_ = QList(); //which rooms to include in the room list QSharedPointer client_; }; diff --git a/include/ui/Theme.h b/include/ui/Theme.h index c6c39553..4d482fe3 100644 --- a/include/ui/Theme.h +++ b/include/ui/Theme.h @@ -15,6 +15,7 @@ enum class AvatarType namespace sidebar { static const int SmallSize = 60; static const int NormalSize = 300; +static const int CommunitiesSidebarSize = 64; } // Default font size. const int FontSize = 16; diff --git a/resources/icons/ui/world.png b/resources/icons/ui/world.png new file mode 100644 index 00000000..d687d141 Binary files /dev/null and b/resources/icons/ui/world.png differ diff --git a/resources/icons/ui/world.svg b/resources/icons/ui/world.svg new file mode 100644 index 00000000..c3acf162 --- /dev/null +++ b/resources/icons/ui/world.svg @@ -0,0 +1,98 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/resources/res.qrc b/resources/res.qrc index 55962275..a3ba1b98 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -24,6 +24,7 @@ icons/ui/angle-pointing-to-left@2x.png icons/ui/angle-arrow-down.png icons/ui/angle-arrow-down@2x.png + icons/ui/world.png icons/emoji-categories/people.png icons/emoji-categories/people@2x.png @@ -40,9 +41,7 @@ icons/emoji-categories/symbols.png icons/emoji-categories/symbols@2x.png icons/emoji-categories/flags.png - icons/emoji-categories/flags@2x.png - nheko.png @@ -62,13 +61,19 @@ nheko-32.png nheko-16.png - fonts/OpenSans/OpenSans-Regular.ttf fonts/OpenSans/OpenSans-Italic.ttf fonts/OpenSans/OpenSans-Bold.ttf fonts/OpenSans/OpenSans-Semibold.ttf +<<<<<<< HEAD +======= + fonts/OpenSans/OpenSans-SemiboldItalic.ttf + fonts/OpenSans/OpenSans-ExtraBold.ttf + fonts/OpenSans/OpenSans-ExtraBoldItalic.ttf +>>>>>>> 4e60338... Initial implementation of Communities (groups) feature fonts/EmojiOne/emojione-android.ttf + diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 5fefd767..12e9b82a 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -55,6 +55,17 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) topLayout_->setSpacing(0); topLayout_->setMargin(0); + communitiesSideBar_ = new QWidget(this); + communitiesSideBar_->setFixedWidth(ui::sidebar::CommunitiesSidebarSize); + communitiesSideBarLayout_ = new QVBoxLayout(communitiesSideBar_); + communitiesSideBarLayout_->setSpacing(0); + communitiesSideBarLayout_->setMargin(0); + + communitiesList_ = new CommunitiesList(client, this); + communitiesSideBarLayout_->addWidget(communitiesList_); + //communitiesSideBarLayout_->addStretch(1); + topLayout_->addWidget(communitiesSideBar_); + auto splitter = new Splitter(this); splitter->setHandleWidth(0); @@ -241,6 +252,26 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) SIGNAL(getOwnProfileResponse(const QUrl &, const QString &)), this, SLOT(updateOwnProfileInfo(const QUrl &, const QString &))); + connect(client_.data(), + SIGNAL(getOwnCommunitiesResponse(QList)), + this, + SLOT(updateOwnCommunitiesInfo(QList))); + connect(client_.data(), &MatrixClient::communityProfileRetrieved, this, + [=](QString communityId, QJsonObject profile) { + communityManager_[communityId]->parseProfile(profile); + }); + connect(client_.data(), &MatrixClient::communityRoomsRetrieved, this, + [=](QString communityId, QJsonObject rooms) { + communityManager_[communityId]->parseRooms(rooms); + + if (communityId == current_community_) { + if (communityId == "world") { + room_list_->setFilterRooms(false); + } else { + room_list_->setRoomFilter(communityManager_[communityId]->getRoomList()); + } + } + }); connect(client_.data(), SIGNAL(ownAvatarRetrieved(const QPixmap &)), this, @@ -270,6 +301,15 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) } }); + connect(communitiesList_, &CommunitiesList::communityChanged, this, [=](const QString &communityId) { + current_community_ = communityId; + if (communityId == "world") { + room_list_->setFilterRooms(false); + } else { + room_list_->setRoomFilter(communityManager_[communityId]->getRoomList()); + } + }); + AvatarProvider::init(client); } @@ -323,6 +363,7 @@ ChatPage::bootstrap(QString userid, QString homeserver, QString token) client_->setServer(homeserver); client_->setAccessToken(token); client_->getOwnProfile(); + client_->getOwnCommunities(); cache_ = QSharedPointer(new Cache(userid)); @@ -445,6 +486,18 @@ ChatPage::updateOwnProfileInfo(const QUrl &avatar_url, const QString &display_na client_->fetchOwnAvatar(avatar_url); } +void +ChatPage::updateOwnCommunitiesInfo(const QList &own_communities) +{ + for (int i = 0; i < own_communities.size(); i++) { + QSharedPointer community = QSharedPointer(new Community()); + + communityManager_[own_communities[i]] = community; + } + + communitiesList_->setCommunities(communityManager_); +} + void ChatPage::changeTopRoomInfo(const QString &room_id) { diff --git a/src/CommunitiesList.cc b/src/CommunitiesList.cc new file mode 100644 index 00000000..739ad4d6 --- /dev/null +++ b/src/CommunitiesList.cc @@ -0,0 +1,143 @@ +#include "CommunitiesList.h" + +#include + +CommunitiesList::CommunitiesList(QSharedPointer client, QWidget *parent) + : QWidget(parent) + , client_(client) +{ + QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(1); + setSizePolicy(sizePolicy); + + setStyleSheet("border-style: none;"); + + topLayout_ = new QVBoxLayout(this); + topLayout_->setSpacing(0); + topLayout_->setMargin(0); + + setFixedWidth(ui::sidebar::CommunitiesSidebarSize); + + scrollArea_ = new QScrollArea(this); + scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); + scrollArea_->setWidgetResizable(true); + scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); + + scrollAreaContents_ = new QWidget(); + + contentsLayout_ = new QVBoxLayout(scrollAreaContents_); + contentsLayout_->setSpacing(0); + contentsLayout_->setMargin(0); + + WorldCommunityListItem *world_list_item = new WorldCommunityListItem(); + contentsLayout_->addWidget(world_list_item); + communities_.insert("world", QSharedPointer(world_list_item)); + connect(world_list_item, &WorldCommunityListItem::clicked, + this, &CommunitiesList::highlightSelectedCommunity); + contentsLayout_->addStretch(1); + + scrollArea_->setWidget(scrollAreaContents_); + topLayout_->addWidget(scrollArea_); + + connect(client_.data(), &MatrixClient::communityProfileRetrieved, this, + [=](QString communityId, QJsonObject profile) { + client_->fetchCommunityAvatar(communityId, QUrl(profile["avatar_url"].toString())); + }); + connect(client_.data(), + SIGNAL(communityAvatarRetrieved(const QString &, const QPixmap &)), + this, + SLOT(updateCommunityAvatar(const QString &,const QPixmap &))); +} + +CommunitiesList::~CommunitiesList() {} + +void +CommunitiesList::setCommunities(const QMap> &communities) +{ + communities_.clear(); + + //TODO: still not sure how to handle the "world" special-case + WorldCommunityListItem *world_list_item = new WorldCommunityListItem(); + communities_.insert("world", QSharedPointer(world_list_item)); + connect(world_list_item, &WorldCommunityListItem::clicked, + this, &CommunitiesList::highlightSelectedCommunity); + contentsLayout_->insertWidget(0, world_list_item); + + for (auto it = communities.constBegin(); it != communities.constEnd(); it++) { + const auto community_id = it.key(); + const auto community = it.value(); + + addCommunity(community, community_id); + + client_->fetchCommunityProfile(community_id); + client_->fetchCommunityRooms(community_id); + } + + world_list_item->setPressedState(true); + emit communityChanged("world"); +} + +void +CommunitiesList::clear() +{ + communities_.clear(); +} + +void +CommunitiesList::addCommunity(QSharedPointer community, const QString &community_id) +{ + CommunitiesListItem *list_item = new CommunitiesListItem(community, + community_id, + scrollArea_); + + communities_.insert(community_id, QSharedPointer(list_item)); + + client_->fetchCommunityAvatar(community_id, community->getAvatar()); + + contentsLayout_->insertWidget(contentsLayout_->count()-1, list_item); + + connect(list_item, &CommunitiesListItem::clicked, + this, &CommunitiesList::highlightSelectedCommunity); +} + +void +CommunitiesList::removeCommunity(const QString &community_id) +{ + communities_.remove(community_id); +} + +void +CommunitiesList::updateCommunityAvatar(const QString &community_id, const QPixmap &img) +{ + if (!communities_.contains(community_id)) { + qWarning() << "Avatar update on nonexistent community" << community_id; + return; + } + + communities_.value(community_id)->setAvatar(img.toImage()); + +} + +void +CommunitiesList::highlightSelectedCommunity(const QString &community_id) +{ + emit communityChanged(community_id); + + if (!communities_.contains(community_id)) { + qDebug() << "CommunitiesList: clicked unknown community"; + return; + } + + for (auto it = communities_.constBegin(); it != communities_.constEnd(); it++) { + if (it.key() != community_id) { + it.value()->setPressedState(false); + } else { + it.value()->setPressedState(true); + scrollArea_->ensureWidgetVisible( + qobject_cast(it.value().data())); + } + } +} diff --git a/src/CommunitiesListItem.cc b/src/CommunitiesListItem.cc new file mode 100644 index 00000000..1a2c1d7c --- /dev/null +++ b/src/CommunitiesListItem.cc @@ -0,0 +1,200 @@ +#include "CommunitiesListItem.h" + +CommunitiesListItem::CommunitiesListItem(QSharedPointer community, + QString community_id, + QWidget *parent) + : QWidget(parent) + , community_(community) + , communityId_(community_id) +{ + //menu_ = new Menu(this); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFixedHeight(ui::sidebar::CommunitiesSidebarSize); + setFixedWidth(ui::sidebar::CommunitiesSidebarSize); +} + +CommunitiesListItem::~CommunitiesListItem() {} + +void +CommunitiesListItem::setCommunity(QSharedPointer community) +{ + community_ = community; +} + +void +CommunitiesListItem::setPressedState(bool state) +{ + if (isPressed_ != state) { + isPressed_ = state; + update(); + } +} + +void +CommunitiesListItem::mousePressEvent(QMouseEvent *event) { + if (event->buttons() == Qt::RightButton) { + QWidget::mousePressEvent(event); + return; + } + + emit clicked(communityId_); + + setPressedState(true); +} + +void +CommunitiesListItem::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + + QPainter p(this); + p.setRenderHint(QPainter::TextAntialiasing); + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.setRenderHint(QPainter::Antialiasing); + + if (isPressed_) + p.fillRect(rect(), QColor("#38A3D8")); + else if (underMouse()) + p.fillRect(rect(), QColor(200, 200, 200, 128)); + else + p.fillRect(rect(), QColor("#FFF")); + + QFont font; + font.setPixelSize(conf::fontSize); + + p.setPen(QColor("#333")); + + QRect avatarRegion((width()-IconSize)/2, (height()-IconSize)/2, IconSize, IconSize); + + font.setBold(false); + p.setPen(Qt::NoPen); + + // We using the first letter of room's name. + if (communityAvatar_.isNull()) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor("#eee"); + + p.setPen(Qt::NoPen); + p.setBrush(brush); + + p.drawEllipse(avatarRegion.center(), IconSize / 2, IconSize / 2); + + font.setPixelSize(conf::roomlist::fonts::bubble); + p.setFont(font); + p.setPen(QColor("#000")); + p.setBrush(Qt::NoBrush); + p.drawText( + avatarRegion.translated(0, -1), Qt::AlignCenter, QChar(community_->getName()[0])); + } else { + p.save(); + + QPainterPath path; + path.addEllipse((width()-IconSize)/2, (height()-IconSize)/2, IconSize, IconSize); + p.setClipPath(path); + + p.drawPixmap(avatarRegion, communityAvatar_); + p.restore(); + } + + //TODO: Discord-style community ping counts? + /*if (unreadMsgCount_ > 0) { + QColor textColor("white"); + QColor backgroundColor("#38A3D8"); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor); + + if (isPressed_) + brush.setColor(textColor); + + QFont unreadCountFont; + unreadCountFont.setPixelSize(conf::roomlist::fonts::badge); + unreadCountFont.setBold(true); + + p.setBrush(brush); + p.setPen(Qt::NoPen); + p.setFont(unreadCountFont); + + int diameter = 20; + + QRectF r( + width() - diameter - 5, height() - diameter - 5, diameter, diameter); + + p.setPen(Qt::NoPen); + p.drawEllipse(r); + + p.setPen(QPen(textColor)); + + if (isPressed_) + p.setPen(QPen(backgroundColor)); + + p.setBrush(Qt::NoBrush); + p.drawText( + r.translated(0, -0.5), Qt::AlignCenter, QString::number(unreadMsgCount_)); + }*/ +} + +void +CommunitiesListItem::contextMenuEvent(QContextMenuEvent *event) +{ + Q_UNUSED(event); + + //menu_->popup(event->globalPos()); +} + +WorldCommunityListItem::WorldCommunityListItem(QWidget *parent) + : CommunitiesListItem(QSharedPointer(), "", parent) +{ +} + +WorldCommunityListItem::~WorldCommunityListItem() {} + +void +WorldCommunityListItem::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() == Qt::RightButton) { + QWidget::mousePressEvent(event); + return; + } + + emit CommunitiesListItem::clicked("world"); + + setPressedState(true); +} + +void +WorldCommunityListItem::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + static QPixmap worldIcon(":/icons/icons/world.png"); + + QPainter p(this); + p.setRenderHint(QPainter::SmoothPixmapTransform); + p.setRenderHint(QPainter::Antialiasing); + + if (isPressed()) + p.fillRect(rect(), QColor("#38A3D8")); + else if (underMouse()) + p.fillRect(rect(), QColor(200, 200, 200, 128)); + else + p.fillRect(rect(), QColor("#FFF")); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor("#FFFFFF"); + + p.setPen(Qt::NoPen); + p.setBrush(brush); + + QRect avatarRegion((width()-IconSize)/2, (height()-IconSize)/2, IconSize, IconSize); + p.drawEllipse(avatarRegion.center(), IconSize / 2, IconSize / 2); + QPainterPath path; + path.addEllipse((width()-IconSize)/2, (height()-IconSize)/2, IconSize, IconSize); + p.setClipPath(path); + + p.drawPixmap(avatarRegion, worldIcon); +} diff --git a/src/Community.cc b/src/Community.cc new file mode 100644 index 00000000..79b4b45b --- /dev/null +++ b/src/Community.cc @@ -0,0 +1,45 @@ +#include "include/Community.h" + +#include +#include + +void +Community::parseProfile(const QJsonObject &profile) +{ + if (profile["name"].type() == QJsonValue::Type::String) { + name_ = profile["name"].toString(); + } else { + name_ = "Unnamed Community"; //TODO: what is correct here? + } + + if (profile["avatar_url"].type() == QJsonValue::Type::String) { + avatar_ = QUrl(profile["avatar_url"].toString()); + } else { + avatar_ = QUrl(); + } + + if (profile["short_description"].type() == QJsonValue::Type::String) { + short_description_ = profile["short_description"].toString(); + } else { + short_description_ = ""; + } + + if (profile["long_description"].type() == QJsonValue::Type::String) { + long_description_ = profile["long_description"].toString(); + } else { + long_description_ = ""; + } + +} + +void +Community::parseRooms(const QJsonObject &rooms) +{ + rooms_.clear(); + + for (auto i = 0; ideleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto data = reply->readAll(); + auto json = QJsonDocument::fromJson(data); + try { + QList response; + for (auto it = json["groups"].toArray().constBegin(); it != json["groups"].toArray().constEnd(); it++) { + response.append(it->toString()); + } + emit getOwnCommunitiesResponse(response); + } catch (DeserializationException &e) { + qWarning() << "Own communities:" << e.what(); + } + }); +} + void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url) { @@ -509,6 +546,112 @@ MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url) }); } +void +MatrixClient::fetchCommunityAvatar(const QString &communityid, const QUrl &avatar_url) +{ + QList url_parts = avatar_url.toString().split("mxc://"); + + if (url_parts.size() != 2) { + qDebug() << "Invalid format for community avatar " << avatar_url.toString(); + return; + } + + QUrlQuery query; + query.addQueryItem("width", "512"); + query.addQueryItem("height", "512"); + query.addQueryItem("method", "crop"); + + QString media_url = + QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]); + + QUrl endpoint(media_url); + endpoint.setQuery(query); + + QNetworkRequest avatar_request(endpoint); + + QNetworkReply *reply = get(avatar_request); + connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto img = reply->readAll(); + + if (img.size() == 0) + return; + + QPixmap pixmap; + pixmap.loadFromData(img); + + emit communityAvatarRetrieved(communityId, pixmap); + }); +} + +void +MatrixClient::fetchCommunityProfile(const QString &communityId) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/profile"); + endpoint.setQuery(query); + + QNetworkRequest request(QString(endpoint.toEncoded())); + + QNetworkReply *reply = get(request); + connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto data = reply->readAll(); + const auto json = QJsonDocument::fromJson(data).object(); + + emit communityProfileRetrieved(communityId, json); + }); +} + +void +MatrixClient::fetchCommunityRooms(const QString &communityId) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(clientApiUrl_ + "/groups/" + communityId + "/rooms"); + endpoint.setQuery(query); + + QNetworkRequest request(QString(endpoint.toEncoded())); + + QNetworkReply *reply = get(request); + connect(reply, &QNetworkReply::finished, this, [this, reply, communityId]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto data = reply->readAll(); + const auto json = QJsonDocument::fromJson(data).object(); + + emit communityRoomsRetrieved(communityId, json); + }); +} + void MatrixClient::fetchUserAvatar(const QString &userId, const QUrl &avatarUrl) { diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc index 857189b5..7692dce6 100644 --- a/src/RoomInfoListItem.cc +++ b/src/RoomInfoListItem.cc @@ -262,10 +262,7 @@ RoomInfoListItem::clearUnreadMessageCount() void RoomInfoListItem::setPressedState(bool state) { - if (!isPressed_ && state) { - isPressed_ = state; - update(); - } else if (isPressed_ && !state) { + if (isPressed_ != state) { isPressed_ = state; update(); } diff --git a/src/RoomList.cc b/src/RoomList.cc index c89e4e6e..2ddc1322 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc @@ -49,7 +49,7 @@ RoomList::RoomList(QSharedPointer client, QWidget *parent) scrollArea_->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea_->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); scrollArea_->setWidgetResizable(true); - scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + scrollArea_->setAlignment(Qt::AlignLeading | Qt::AlignTop | Qt::AlignVCenter); scrollAreaContents_ = new QWidget(); @@ -89,6 +89,8 @@ RoomList::addRoom(const QSharedPointer &settings, client_->fetchRoomAvatar(room_id, state.getAvatar()); contentsLayout_->insertWidget(0, room_item); + + setFilterRooms(filterRooms_); } void @@ -164,6 +166,8 @@ RoomList::setInitialRooms(const QMap> &set if (rooms_.isEmpty()) return; + setFilterRooms(filterRooms_); + auto first_room = rooms_.first(); first_room->setPressedState(true); @@ -241,6 +245,8 @@ RoomList::highlightSelectedRoom(const QString &room_id) qobject_cast(it.value().data())); } } + + selectedRoom_ = room_id; } void @@ -284,3 +290,47 @@ RoomList::closeLeaveRoomDialog(bool leaving, const QString &room_id) client_->leaveRoom(room_id); } } + +void +RoomList::setFilterRooms(bool filterRooms) +{ + filterRooms_ = filterRooms; + + for (int i=0; icount(); i++) { + + //If roomFilter_ contains the room for the current RoomInfoListItem, + //show the list item, otherwise hide it + RoomInfoListItem *listitem = (RoomInfoListItem *) contentsLayout_->itemAt(i)->widget(); + + if (listitem != nullptr) { + if (!filterRooms) { + contentsLayout_->itemAt(i)->widget()->show(); + } else if (roomFilter_.contains(listitem->roomId())) { + contentsLayout_->itemAt(i)->widget()->show(); + } else { + contentsLayout_->itemAt(i)->widget()->hide(); + } + } + } + + if (!roomFilter_.contains(selectedRoom_)) { + RoomInfoListItem *firstVisibleRoom = nullptr; + for (int i=0; icount(); i++) { + QWidget *item = contentsLayout_->itemAt(i)->widget(); + if (item != nullptr && item->isVisible()) { + firstVisibleRoom = (RoomInfoListItem *) item; + break; + } + } + if (firstVisibleRoom != nullptr) { + highlightSelectedRoom(firstVisibleRoom->roomId()); + } + } +} + +void +RoomList::setRoomFilter(QList room_ids) +{ + roomFilter_ = room_ids; + setFilterRooms(true); +}