diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b36ad11..51e1993b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,13 @@ find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5LinguistTools REQUIRED) +if (Qt5Widgets_FOUND) + if (Qt5Widgets_VERSION VERSION_LESS 5.7.0) + message(STATUS "Qt version ${Qt5Widgets_VERSION}") + message(WARNING "Minimum supported Qt5 version is 5.7!") + endif() +endif(Qt5Widgets_FOUND) + set(CMAKE_C_COMPILER gcc) set(CMAKE_CXX_STANDARD 11) @@ -51,7 +58,6 @@ message(STATUS "Version: ${PROJECT_VERSION}") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ - -std=c++11 \ -Wall \ -Wextra \ -Werror \ @@ -72,6 +78,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") endif() set(SRC_FILES + src/AvatarProvider.cc src/ChatPage.cc src/Deserializable.cc src/EmojiCategory.cc @@ -88,6 +95,7 @@ set(SRC_FILES src/Login.cc src/LoginPage.cc src/LoginSettings.cc + src/LogoutDialog.cc src/MainWindow.cc src/MatrixClient.cc src/Profile.cc @@ -153,6 +161,7 @@ include_directories(include/events) include_directories(include/events/messages) qt5_wrap_cpp(MOC_HEADERS + include/AvatarProvider.h include/ChatPage.h include/EmojiCategory.h include/EmojiItemDelegate.h @@ -165,6 +174,7 @@ qt5_wrap_cpp(MOC_HEADERS include/TimelineViewManager.h include/LoginPage.h include/LoginSettings.h + include/LogoutDialog.h include/MainWindow.h include/MatrixClient.h include/RegisterPage.h diff --git a/README.md b/README.md index 56b1b5ca..591190d5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ nheko ---- -[![Build Status](https://travis-ci.org/mujx/nheko.svg?branch=master)](https://travis-ci.org/mujx/nheko) [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/mujx/nheko/branch/master) +[![Build Status](https://travis-ci.org/mujx/nheko.svg?branch=master)](https://travis-ci.org/mujx/nheko) [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/mujx/nheko/branch/master) [![Translation Status](https://translate.nordgedanken.de/widgets/nheko/-/shields-badge.svg)](https://translate.nordgedanken.de/projects/nheko/nheko/) The motivation behind the project is to provide a native desktop app for [Matrix] that feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client. diff --git a/include/AvatarProvider.h b/include/AvatarProvider.h new file mode 100644 index 00000000..29c8152b --- /dev/null +++ b/include/AvatarProvider.h @@ -0,0 +1,47 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#pragma once + +#include +#include +#include +#include + +#include "MatrixClient.h" +#include "TimelineItem.h" + +class AvatarProvider : public QObject +{ + Q_OBJECT + +public: + static void init(QSharedPointer client); + static void resolve(const QString &userId, TimelineItem *item); + static void setAvatarUrl(const QString &userId, const QUrl &url); + + static void clear(); + +private: + static void updateAvatar(const QString &uid, const QImage &img); + + static QSharedPointer client_; + static QMap> toBeResolved_; + + static QMap userAvatars_; + static QMap avatarUrls_; +}; diff --git a/include/ChatPage.h b/include/ChatPage.h index 68495276..74db6b15 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -23,6 +23,7 @@ #include "MatrixClient.h" #include "RoomList.h" +#include "RoomSettings.h" #include "RoomState.h" #include "Splitter.h" #include "TextInputWidget.h" @@ -92,6 +93,7 @@ private: UserInfoWidget *user_info_widget_; QMap state_manager_; + QMap> settingsManager_; // Matrix Client API provider. QSharedPointer client_; diff --git a/include/EmojiPanel.h b/include/EmojiPanel.h index e87ab7a4..14b7692a 100644 --- a/include/EmojiPanel.h +++ b/include/EmojiPanel.h @@ -17,9 +17,7 @@ #pragma once -#include #include -#include #include #include #include @@ -27,7 +25,7 @@ #include "EmojiCategory.h" #include "EmojiProvider.h" -class EmojiPanel : public QFrame +class EmojiPanel : public QWidget { Q_OBJECT @@ -43,25 +41,24 @@ signals: protected: void leaveEvent(QEvent *event); + void paintEvent(QPaintEvent *event); private: void showEmojiCategory(const EmojiCategory *category); - QPropertyAnimation *opacity_anim_; - QPropertyAnimation *size_anim_; + QPropertyAnimation *animation_; QGraphicsOpacityEffect *opacity_; - QParallelAnimationGroup *animation_; EmojiProvider emoji_provider_; - QScrollArea *scroll_area_; + QScrollArea *scrollArea_; + + int shadowMargin_; // Panel dimensions. - const int WIDTH = 370; - const int HEIGHT = 350; + int width_; + int height_; - const int ANIMATION_DURATION = 100; - const int ANIMATION_OFFSET = 50; - - const int category_icon_size_ = 20; + int animationDuration_; + int categoryIconSize_; }; diff --git a/include/LogoutDialog.h b/include/LogoutDialog.h new file mode 100644 index 00000000..a2d313c7 --- /dev/null +++ b/include/LogoutDialog.h @@ -0,0 +1,36 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#pragma once + +#include + +#include "FlatButton.h" + +class LogoutDialog : public QFrame +{ + Q_OBJECT +public: + explicit LogoutDialog(QWidget *parent = nullptr); + +signals: + void closing(bool isLoggingOut); + +private: + FlatButton *confirmBtn_; + FlatButton *cancelBtn_; +}; diff --git a/include/MainWindow.h b/include/MainWindow.h index 536cea13..9d0a601f 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -38,6 +38,8 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); + static MainWindow *instance(); + protected: void closeEvent(QCloseEvent *event); @@ -62,6 +64,8 @@ private slots: private: bool hasActiveUser(); + static MainWindow *instance_; + // The initial welcome screen. WelcomePage *welcome_page_; diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 2fdb57f0..2fde1c1e 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -41,6 +41,7 @@ public: void registerUser(const QString &username, const QString &password, const QString &server) noexcept; void versions() noexcept; void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url); + void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl); 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) noexcept; @@ -69,6 +70,7 @@ signals: void registerSuccess(const QString &userid, const QString &homeserver, const QString &token); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); + void userAvatarRetrieved(const QString &userId, const QImage &img); void ownAvatarRetrieved(const QPixmap &img); void imageDownloaded(const QString &event_id, const QPixmap &img); @@ -95,6 +97,7 @@ private: Messages, Register, RoomAvatar, + UserAvatar, SendTextMessage, Sync, Versions, @@ -111,6 +114,7 @@ private: void onInitialSyncResponse(QNetworkReply *reply); void onSyncResponse(QNetworkReply *reply); void onRoomAvatarResponse(QNetworkReply *reply); + void onUserAvatarResponse(QNetworkReply *reply); void onImageResponse(QNetworkReply *reply); void onMessagesResponse(QNetworkReply *reply); diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h index 17c75fc3..40a1cbb0 100644 --- a/include/RoomInfoListItem.h +++ b/include/RoomInfoListItem.h @@ -17,9 +17,13 @@ #pragma once +#include +#include #include +#include "Menu.h" #include "RippleOverlay.h" +#include "RoomSettings.h" #include "RoomState.h" class RoomInfoListItem : public QWidget @@ -27,7 +31,11 @@ class RoomInfoListItem : public QWidget Q_OBJECT public: - RoomInfoListItem(RoomState state, QString room_id, QWidget *parent = 0); + RoomInfoListItem(QSharedPointer settings, + RoomState state, + QString room_id, + QWidget *parent = 0); + ~RoomInfoListItem(); void updateUnreadMessageCount(int count); @@ -48,8 +56,11 @@ public slots: protected: void mousePressEvent(QMouseEvent *event) override; void paintEvent(QPaintEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; private: + QString notificationText(); + const int Padding = 7; const int IconSize = 46; @@ -64,6 +75,11 @@ private: QPixmap roomAvatar_; + Menu *menu_; + QAction *toggleNotifications_; + + QSharedPointer roomSettings_; + bool isPressed_ = false; int maxHeight_; diff --git a/include/RoomList.h b/include/RoomList.h index 489083d1..417ed363 100644 --- a/include/RoomList.h +++ b/include/RoomList.h @@ -35,7 +35,8 @@ public: RoomList(QSharedPointer client, QWidget *parent = 0); ~RoomList(); - void setInitialRooms(const QMap &states); + void setInitialRooms(const QMap> &settings, + const QMap &states); void sync(const QMap &states); void clear(); diff --git a/include/RoomSettings.h b/include/RoomSettings.h new file mode 100644 index 00000000..ee74b9eb --- /dev/null +++ b/include/RoomSettings.h @@ -0,0 +1,55 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#pragma once + +#include + +class RoomSettings +{ +public: + RoomSettings(QString room_id) + { + path_ = QString("notifications/%1").arg(room_id); + isNotificationsEnabled_ = true; + + QSettings settings; + + if (settings.contains(path_)) + isNotificationsEnabled_ = settings.value(path_).toBool(); + else + settings.setValue(path_, isNotificationsEnabled_); + }; + + bool isNotificationsEnabled() + { + return isNotificationsEnabled_; + }; + + void toggleNotifications() + { + isNotificationsEnabled_ = !isNotificationsEnabled_; + + QSettings settings; + settings.setValue(path_, isNotificationsEnabled_); + } + +private: + QString path_; + + bool isNotificationsEnabled_; +}; diff --git a/include/TimelineItem.h b/include/TimelineItem.h index 5db823b0..c0cf1c7b 100644 --- a/include/TimelineItem.h +++ b/include/TimelineItem.h @@ -24,6 +24,7 @@ #include "ImageItem.h" #include "Sync.h" +#include "Avatar.h" #include "Image.h" #include "MessageEvent.h" #include "Notice.h" @@ -46,19 +47,35 @@ public: TimelineItem(ImageItem *img, const events::MessageEvent &e, const QString &color, QWidget *parent); TimelineItem(ImageItem *img, const events::MessageEvent &e, QWidget *parent); + void setUserAvatar(const QImage &pixmap); + ~TimelineItem(); private: + void init(); + void generateBody(const QString &body); void generateBody(const QString &userid, const QString &color, const QString &body); void generateTimestamp(const QDateTime &time); + void setupAvatarLayout(const QString &userName); + void setupSimpleLayout(); + QString replaceEmoji(const QString &body); - void setupLayout(); + QHBoxLayout *topLayout_; + QVBoxLayout *sideLayout_; // Avatar or Timestamp + QVBoxLayout *mainLayout_; // Header & Message body - QHBoxLayout *top_layout_; + QHBoxLayout *headerLayout_; // Username (&) Timestamp - QLabel *time_label_; - QLabel *content_label_; + Avatar *userAvatar_; + + QLabel *timestamp_; + QLabel *userName_; + QLabel *body_; + + QFont bodyFont_; + QFont usernameFont_; + QFont timestampFont_; }; diff --git a/include/TopRoomBar.h b/include/TopRoomBar.h index 304ec320..6e4bfe7a 100644 --- a/include/TopRoomBar.h +++ b/include/TopRoomBar.h @@ -17,15 +17,20 @@ #pragma once +#include +#include #include #include #include #include +#include #include #include #include "Avatar.h" #include "FlatButton.h" +#include "Menu.h" +#include "RoomSettings.h" class TopRoomBar : public QWidget { @@ -39,6 +44,7 @@ public: inline void updateRoomName(const QString &name); inline void updateRoomTopic(const QString &topic); void updateRoomAvatarFromName(const QString &name); + void setRoomSettings(QSharedPointer settings); void reset(); @@ -52,10 +58,16 @@ private: QLabel *name_label_; QLabel *topic_label_; - FlatButton *search_button_; - FlatButton *settings_button_; + QSharedPointer roomSettings_; + + QMenu *menu_; + QAction *toggleNotifications_; + + FlatButton *settingsBtn_; Avatar *avatar_; + + int buttonSize_; }; inline void TopRoomBar::updateRoomAvatar(const QImage &avatar_image) diff --git a/include/UserInfoWidget.h b/include/UserInfoWidget.h index 1b819577..10c770d8 100644 --- a/include/UserInfoWidget.h +++ b/include/UserInfoWidget.h @@ -24,6 +24,8 @@ #include "Avatar.h" #include "FlatButton.h" +#include "LogoutDialog.h" +#include "OverlayModal.h" class UserInfoWidget : public QWidget { @@ -45,6 +47,9 @@ signals: protected: void resizeEvent(QResizeEvent *event) override; +private slots: + void closeLogoutDialog(bool isLoggingOut); + private: Avatar *userAvatar_; @@ -62,4 +67,9 @@ private: QString user_id_; QImage avatar_image_; + + OverlayModal *logoutModal_; + LogoutDialog *logoutDialog_; + + int logoutButtonSize_; }; diff --git a/include/ui/DropShadow.h b/include/ui/DropShadow.h new file mode 100644 index 00000000..19d61c8d --- /dev/null +++ b/include/ui/DropShadow.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include + +class DropShadow +{ +public: + static void draw(QPainter &painter, + qint16 margin, + qreal radius, + QColor start, + QColor end, + qreal startPosition, + qreal endPosition0, + qreal endPosition1, + qreal width, + qreal height) + { + painter.setPen(Qt::NoPen); + + QLinearGradient gradient; + gradient.setColorAt(startPosition, start); + gradient.setColorAt(endPosition0, end); + + // Right + QPointF right0(width - margin, height / 2); + QPointF right1(width, height / 2); + gradient.setStart(right0); + gradient.setFinalStop(right1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(QPointF(width - margin * radius, margin), QPointF(width, height - margin)), 0.0, 0.0); + + // Left + QPointF left0(margin, height / 2); + QPointF left1(0, height / 2); + gradient.setStart(left0); + gradient.setFinalStop(left1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(QPointF(margin * radius, margin), QPointF(0, height - margin)), 0.0, 0.0); + + // Top + QPointF top0(width / 2, margin); + QPointF top1(width / 2, 0); + gradient.setStart(top0); + gradient.setFinalStop(top1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(QPointF(width - margin, 0), QPointF(margin, margin)), 0.0, 0.0); + + // Bottom + QPointF bottom0(width / 2, height - margin); + QPointF bottom1(width / 2, height); + gradient.setStart(bottom0); + gradient.setFinalStop(bottom1); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(QPointF(margin, height - margin), QPointF(width - margin, height)), 0.0, 0.0); + + // BottomRight + QPointF bottomright0(width - margin, height - margin); + QPointF bottomright1(width, height); + gradient.setStart(bottomright0); + gradient.setFinalStop(bottomright1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(bottomright0, bottomright1), 0.0, 0.0); + + // BottomLeft + QPointF bottomleft0(margin, height - margin); + QPointF bottomleft1(0, height); + gradient.setStart(bottomleft0); + gradient.setFinalStop(bottomleft1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(bottomleft0, bottomleft1), 0.0, 0.0); + + // TopLeft + QPointF topleft0(margin, margin); + QPointF topleft1(0, 0); + gradient.setStart(topleft0); + gradient.setFinalStop(topleft1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(topleft0, topleft1), 0.0, 0.0); + + // TopRight + QPointF topright0(width - margin, margin); + QPointF topright1(width, 0); + gradient.setStart(topright0); + gradient.setFinalStop(topright1); + gradient.setColorAt(endPosition1, end); + painter.setBrush(QBrush(gradient)); + painter.drawRoundRect(QRectF(topright0, topright1), 0.0, 0.0); + + // Widget + painter.setBrush(QBrush("#FFFFFF")); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawRoundRect(QRectF(QPointF(margin, margin), QPointF(width - margin, height - margin)), radius, radius); + } +}; diff --git a/include/ui/Menu.h b/include/ui/Menu.h new file mode 100644 index 00000000..44c13b79 --- /dev/null +++ b/include/ui/Menu.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +class Menu : public QMenu +{ +public: + Menu(QWidget *parent = nullptr) + : QMenu(parent) + { + setFont(QFont("Open Sans", 10)); + setStyleSheet( + "QMenu { color: black; background-color: white; margin: 0px;}" + "QMenu::item { color: black; padding: 7px 20px; border: 1px solid transparent; margin: 2px 0px; }" + "QMenu::item:selected { color: black; background: rgba(180, 180, 180, 100); }"); + }; + +protected: + void leaveEvent(QEvent *e) + { + Q_UNUSED(e); + + hide(); + } +}; diff --git a/resources/icons/vertical-ellipsis.png b/resources/icons/vertical-ellipsis.png new file mode 100644 index 00000000..82714451 Binary files /dev/null and b/resources/icons/vertical-ellipsis.png differ diff --git a/resources/res.qrc b/resources/res.qrc index be7d8770..56bf7144 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -22,6 +22,7 @@ icons/emoji-categories/symbols.png icons/emoji-categories/flags.png + icons/vertical-ellipsis.png diff --git a/src/AvatarProvider.cc b/src/AvatarProvider.cc new file mode 100644 index 00000000..7481b781 --- /dev/null +++ b/src/AvatarProvider.cc @@ -0,0 +1,83 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#include "AvatarProvider.h" + +QSharedPointer AvatarProvider::client_; + +QMap AvatarProvider::userAvatars_; +QMap AvatarProvider::avatarUrls_; +QMap> AvatarProvider::toBeResolved_; + +void AvatarProvider::init(QSharedPointer client) +{ + client_ = client; + + connect(client_.data(), &MatrixClient::userAvatarRetrieved, &AvatarProvider::updateAvatar); +} + +void AvatarProvider::updateAvatar(const QString &uid, const QImage &img) +{ + if (toBeResolved_.contains(uid)) { + auto items = toBeResolved_[uid]; + + // Update all the timeline items with the resolved avatar. + for (const auto item : items) + item->setUserAvatar(img); + + toBeResolved_.remove(uid); + } + + userAvatars_.insert(uid, img); +} + +void AvatarProvider::resolve(const QString &userId, TimelineItem *item) +{ + if (userAvatars_.contains(userId)) { + auto img = userAvatars_[userId]; + + item->setUserAvatar(img); + + return; + } + + if (avatarUrls_.contains(userId)) { + // Add the current timeline item to the waiting list for this avatar. + if (!toBeResolved_.contains(userId)) { + client_->fetchUserAvatar(userId, avatarUrls_[userId]); + + QList timelineItems; + timelineItems.push_back(item); + + toBeResolved_.insert(userId, timelineItems); + } else { + toBeResolved_[userId].push_back(item); + } + } +} + +void AvatarProvider::setAvatarUrl(const QString &userId, const QUrl &url) +{ + avatarUrls_.insert(userId, url); +} + +void AvatarProvider::clear() +{ + userAvatars_.clear(); + avatarUrls_.clear(); + toBeResolved_.clear(); +} diff --git a/src/ChatPage.cc b/src/ChatPage.cc index d318d086..4e9120d2 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -27,6 +27,7 @@ #include "AliasesEventContent.h" #include "AvatarEventContent.h" +#include "AvatarProvider.h" #include "CanonicalAliasEventContent.h" #include "CreateEventContent.h" #include "HistoryVisibilityEventContent.h" @@ -127,10 +128,16 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); - connect(view_manager_, - SIGNAL(unreadMessages(const QString &, int)), - room_list_, - SLOT(updateUnreadMessageCount(const QString &, int))); + connect(view_manager_, &TimelineViewManager::unreadMessages, this, [=](const QString &roomid, int count) { + if (!settingsManager_.contains(roomid)) { + qWarning() << "RoomId does not have settings" << roomid; + room_list_->updateUnreadMessageCount(roomid, count); + return; + } + + if (settingsManager_[roomid]->isNotificationsEnabled()) + room_list_->updateUnreadMessageCount(roomid, count); + }); connect(room_list_, SIGNAL(totalUnreadMessageCountUpdated(int)), @@ -167,17 +174,25 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) SIGNAL(ownAvatarRetrieved(const QPixmap &)), this, SLOT(setOwnAvatar(const QPixmap &))); + + AvatarProvider::init(client); } void ChatPage::logout() { sync_timer_->stop(); + // Delete all config parameters. QSettings settings; - settings.remove("auth/access_token"); - settings.remove("auth/home_server"); - settings.remove("auth/user_id"); - settings.remove("client/transaction_id"); + settings.beginGroup("auth"); + settings.remove(""); + settings.endGroup(); + settings.beginGroup("client"); + settings.remove(""); + settings.endGroup(); + settings.beginGroup("notifications"); + settings.remove(""); + settings.endGroup(); // Clear the environment. room_list_->clear(); @@ -188,8 +203,11 @@ void ChatPage::logout() client_->reset(); state_manager_.clear(); + settingsManager_.clear(); room_avatars_.clear(); + AvatarProvider::clear(); + emit close(); } @@ -286,10 +304,19 @@ void ChatPage::initialSyncCompleted(const SyncResponse &response) updateDisplayNames(room_state); state_manager_.insert(it.key(), room_state); + settingsManager_.insert(it.key(), QSharedPointer(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_->initialize(response.rooms()); - room_list_->setInitialRooms(state_manager_); + room_list_->setInitialRooms(settingsManager_, state_manager_); sync_timer_->start(sync_interval_); } @@ -325,6 +352,7 @@ void ChatPage::changeTopRoomInfo(const QString &room_id) top_bar_->updateRoomName(state.getName()); top_bar_->updateRoomTopic(state.getTopic()); + top_bar_->setRoomSettings(settingsManager_[room_id]); if (room_avatars_.contains(room_id)) top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage()); diff --git a/src/EmojiPanel.cc b/src/EmojiPanel.cc index 63b408c0..53b3f8d1 100644 --- a/src/EmojiPanel.cc +++ b/src/EmojiPanel.cc @@ -15,20 +15,24 @@ * along with this program. If not, see . */ -#include -#include #include #include #include #include #include "Avatar.h" +#include "DropShadow.h" #include "EmojiCategory.h" #include "EmojiPanel.h" #include "FlatButton.h" EmojiPanel::EmojiPanel(QWidget *parent) - : QFrame(parent) + : QWidget(parent) + , shadowMargin_{2} + , width_{370} + , height_{350} + , animationDuration_{100} + , categoryIconSize_{20} { setStyleSheet( "QWidget {background: #f8fbfe; color: #e8e8e8; border: none;}" @@ -40,172 +44,158 @@ EmojiPanel::EmojiPanel(QWidget *parent) setAttribute(Qt::WA_TranslucentBackground, true); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); - // TODO: Make it MainWindow aware - auto main_frame_ = new QFrame(this); - main_frame_->setMaximumSize(WIDTH, HEIGHT); + auto mainWidget = new QWidget(this); + mainWidget->setMaximumSize(width_, height_); - auto top_layout = new QVBoxLayout(this); - top_layout->addWidget(main_frame_); - top_layout->setMargin(0); + auto topLayout = new QVBoxLayout(this); + topLayout->addWidget(mainWidget); + topLayout->setMargin(shadowMargin_); - auto content_layout = new QVBoxLayout(main_frame_); - content_layout->setMargin(0); + auto contentLayout = new QVBoxLayout(mainWidget); + contentLayout->setMargin(0); - auto emoji_categories = new QFrame(main_frame_); - emoji_categories->setStyleSheet("background-color: #f2f2f2"); + auto emojiCategories = new QFrame(mainWidget); + emojiCategories->setStyleSheet("background-color: #f2f2f2"); - auto categories_layout = new QHBoxLayout(emoji_categories); - categories_layout->setSpacing(6); - categories_layout->setMargin(5); + auto categoriesLayout = new QHBoxLayout(emojiCategories); + categoriesLayout->setSpacing(6); + categoriesLayout->setMargin(5); - auto people_category = new FlatButton(emoji_categories); - people_category->setIcon(QIcon(":/icons/icons/emoji-categories/people.png")); - people_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - people_category->setForegroundColor("gray"); + auto peopleCategory = new FlatButton(emojiCategories); + peopleCategory->setIcon(QIcon(":/icons/icons/emoji-categories/people.png")); + peopleCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + peopleCategory->setForegroundColor("gray"); - auto nature_category = new FlatButton(emoji_categories); - nature_category->setIcon(QIcon(":/icons/icons/emoji-categories/nature.png")); - nature_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - nature_category->setForegroundColor("gray"); + auto natureCategory_ = new FlatButton(emojiCategories); + natureCategory_->setIcon(QIcon(":/icons/icons/emoji-categories/nature.png")); + natureCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + natureCategory_->setForegroundColor("gray"); - auto food_category = new FlatButton(emoji_categories); - food_category->setIcon(QIcon(":/icons/icons/emoji-categories/foods.png")); - food_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - food_category->setForegroundColor("gray"); + auto foodCategory_ = new FlatButton(emojiCategories); + foodCategory_->setIcon(QIcon(":/icons/icons/emoji-categories/foods.png")); + foodCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + foodCategory_->setForegroundColor("gray"); - auto activity_category = new FlatButton(emoji_categories); - activity_category->setIcon(QIcon(":/icons/icons/emoji-categories/activity.png")); - activity_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - activity_category->setForegroundColor("gray"); + auto activityCategory = new FlatButton(emojiCategories); + activityCategory->setIcon(QIcon(":/icons/icons/emoji-categories/activity.png")); + activityCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + activityCategory->setForegroundColor("gray"); - auto travel_category = new FlatButton(emoji_categories); - travel_category->setIcon(QIcon(":/icons/icons/emoji-categories/travel.png")); - travel_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - travel_category->setForegroundColor("gray"); + auto travelCategory = new FlatButton(emojiCategories); + travelCategory->setIcon(QIcon(":/icons/icons/emoji-categories/travel.png")); + travelCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + travelCategory->setForegroundColor("gray"); - auto objects_category = new FlatButton(emoji_categories); - objects_category->setIcon(QIcon(":/icons/icons/emoji-categories/objects.png")); - objects_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - objects_category->setForegroundColor("gray"); + auto objectsCategory = new FlatButton(emojiCategories); + objectsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/objects.png")); + objectsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + objectsCategory->setForegroundColor("gray"); - auto symbols_category = new FlatButton(emoji_categories); - symbols_category->setIcon(QIcon(":/icons/icons/emoji-categories/symbols.png")); - symbols_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - symbols_category->setForegroundColor("gray"); + auto symbolsCategory = new FlatButton(emojiCategories); + symbolsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/symbols.png")); + symbolsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + symbolsCategory->setForegroundColor("gray"); - auto flags_category = new FlatButton(emoji_categories); - flags_category->setIcon(QIcon(":/icons/icons/emoji-categories/flags.png")); - flags_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); - flags_category->setForegroundColor("gray"); + auto flagsCategory = new FlatButton(emojiCategories); + flagsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/flags.png")); + flagsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_)); + flagsCategory->setForegroundColor("gray"); - categories_layout->addWidget(people_category); - categories_layout->addWidget(nature_category); - categories_layout->addWidget(food_category); - categories_layout->addWidget(activity_category); - categories_layout->addWidget(travel_category); - categories_layout->addWidget(objects_category); - categories_layout->addWidget(symbols_category); - categories_layout->addWidget(flags_category); + categoriesLayout->addWidget(peopleCategory); + categoriesLayout->addWidget(natureCategory_); + categoriesLayout->addWidget(foodCategory_); + categoriesLayout->addWidget(activityCategory); + categoriesLayout->addWidget(travelCategory); + categoriesLayout->addWidget(objectsCategory); + categoriesLayout->addWidget(symbolsCategory); + categoriesLayout->addWidget(flagsCategory); - scroll_area_ = new QScrollArea(this); - scroll_area_->setWidgetResizable(true); - scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea_ = new QScrollArea(this); + scrollArea_->setWidgetResizable(true); + scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - auto scroll_widget_ = new QWidget(this); - auto scroll_layout_ = new QVBoxLayout(scroll_widget_); + auto scrollWidget = new QWidget(this); + auto scrollLayout = new QVBoxLayout(scrollWidget); - scroll_layout_->setMargin(0); - scroll_area_->setWidget(scroll_widget_); + scrollLayout->setMargin(0); + scrollArea_->setWidget(scrollWidget); - auto people_emoji = new EmojiCategory(tr("Smileys & People"), emoji_provider_.people, scroll_widget_); - scroll_layout_->addWidget(people_emoji); + auto peopleEmoji = new EmojiCategory(tr("Smileys & People"), emoji_provider_.people, scrollWidget); + scrollLayout->addWidget(peopleEmoji); - auto nature_emoji = new EmojiCategory(tr("Animals & Nature"), emoji_provider_.nature, scroll_widget_); - scroll_layout_->addWidget(nature_emoji); + auto natureEmoji = new EmojiCategory(tr("Animals & Nature"), emoji_provider_.nature, scrollWidget); + scrollLayout->addWidget(natureEmoji); - auto food_emoji = new EmojiCategory(tr("Food & Drink"), emoji_provider_.food, scroll_widget_); - scroll_layout_->addWidget(food_emoji); + auto foodEmoji = new EmojiCategory(tr("Food & Drink"), emoji_provider_.food, scrollWidget); + scrollLayout->addWidget(foodEmoji); - auto activity_emoji = new EmojiCategory(tr("Activity"), emoji_provider_.activity, scroll_widget_); - scroll_layout_->addWidget(activity_emoji); + auto activityEmoji = new EmojiCategory(tr("Activity"), emoji_provider_.activity, scrollWidget); + scrollLayout->addWidget(activityEmoji); - auto travel_emoji = new EmojiCategory(tr("Travel & Places"), emoji_provider_.travel, scroll_widget_); - scroll_layout_->addWidget(travel_emoji); + auto travelEmoji = new EmojiCategory(tr("Travel & Places"), emoji_provider_.travel, scrollWidget); + scrollLayout->addWidget(travelEmoji); - auto objects_emoji = new EmojiCategory(tr("Objects"), emoji_provider_.objects, scroll_widget_); - scroll_layout_->addWidget(objects_emoji); + auto objectsEmoji = new EmojiCategory(tr("Objects"), emoji_provider_.objects, scrollWidget); + scrollLayout->addWidget(objectsEmoji); - auto symbols_emoji = new EmojiCategory(tr("Symbols"), emoji_provider_.symbols, scroll_widget_); - scroll_layout_->addWidget(symbols_emoji); + auto symbolsEmoji = new EmojiCategory(tr("Symbols"), emoji_provider_.symbols, scrollWidget); + scrollLayout->addWidget(symbolsEmoji); - auto flags_emoji = new EmojiCategory(tr("Flags"), emoji_provider_.flags, scroll_widget_); - scroll_layout_->addWidget(flags_emoji); + auto flagsEmoji = new EmojiCategory(tr("Flags"), emoji_provider_.flags, scrollWidget); + scrollLayout->addWidget(flagsEmoji); - content_layout->addStretch(1); - content_layout->addWidget(scroll_area_); - content_layout->addWidget(emoji_categories); - - setLayout(top_layout); + contentLayout->addStretch(1); + contentLayout->addWidget(scrollArea_); + contentLayout->addWidget(emojiCategories); opacity_ = new QGraphicsOpacityEffect(this); opacity_->setOpacity(1.0); setGraphicsEffect(opacity_); - opacity_anim_ = new QPropertyAnimation(opacity_, "opacity", this); - opacity_anim_->setDuration(ANIMATION_DURATION); - opacity_anim_->setStartValue(1); - opacity_anim_->setEndValue(0); + animation_ = new QPropertyAnimation(opacity_, "opacity", this); + animation_->setDuration(animationDuration_); + animation_->setStartValue(1); + animation_->setEndValue(0); - size_anim_ = new QPropertyAnimation(this); - size_anim_->setTargetObject(main_frame_); - size_anim_->setPropertyName("geometry"); - size_anim_->setDuration(ANIMATION_DURATION); - size_anim_->setStartValue(QRect(0, 0, WIDTH, HEIGHT)); - size_anim_->setEndValue(QRect(ANIMATION_OFFSET, ANIMATION_OFFSET, WIDTH - ANIMATION_OFFSET, HEIGHT - ANIMATION_OFFSET)); - - animation_ = new QParallelAnimationGroup(this); - animation_->addAnimation(opacity_anim_); - animation_->addAnimation(size_anim_); - - connect(people_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(people_category, &QPushButton::clicked, [this, people_emoji]() { - this->showEmojiCategory(people_emoji); + connect(peopleEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(peopleCategory, &QPushButton::clicked, [this, peopleEmoji]() { + this->showEmojiCategory(peopleEmoji); }); - connect(nature_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(nature_category, &QPushButton::clicked, [this, nature_emoji]() { - this->showEmojiCategory(nature_emoji); + connect(natureEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(natureCategory_, &QPushButton::clicked, [this, natureEmoji]() { + this->showEmojiCategory(natureEmoji); }); - connect(food_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(food_category, &QPushButton::clicked, [this, food_emoji]() { - this->showEmojiCategory(food_emoji); + connect(foodEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(foodCategory_, &QPushButton::clicked, [this, foodEmoji]() { + this->showEmojiCategory(foodEmoji); }); - connect(activity_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(activity_category, &QPushButton::clicked, [this, activity_emoji]() { - this->showEmojiCategory(activity_emoji); + connect(activityEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(activityCategory, &QPushButton::clicked, [this, activityEmoji]() { + this->showEmojiCategory(activityEmoji); }); - connect(travel_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(travel_category, &QPushButton::clicked, [this, travel_emoji]() { - this->showEmojiCategory(travel_emoji); + connect(travelEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(travelCategory, &QPushButton::clicked, [this, travelEmoji]() { + this->showEmojiCategory(travelEmoji); }); - connect(objects_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(objects_category, &QPushButton::clicked, [this, objects_emoji]() { - this->showEmojiCategory(objects_emoji); + connect(objectsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(objectsCategory, &QPushButton::clicked, [this, objectsEmoji]() { + this->showEmojiCategory(objectsEmoji); }); - connect(symbols_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(symbols_category, &QPushButton::clicked, [this, symbols_emoji]() { - this->showEmojiCategory(symbols_emoji); + connect(symbolsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(symbolsCategory, &QPushButton::clicked, [this, symbolsEmoji]() { + this->showEmojiCategory(symbolsEmoji); }); - connect(flags_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); - connect(flags_category, &QPushButton::clicked, [this, flags_emoji]() { - this->showEmojiCategory(flags_emoji); + connect(flagsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); + connect(flagsCategory, &QPushButton::clicked, [this, flagsEmoji]() { + this->showEmojiCategory(flagsEmoji); }); connect(animation_, &QAbstractAnimation::finished, [this]() { @@ -217,7 +207,7 @@ EmojiPanel::EmojiPanel(QWidget *parent) void EmojiPanel::showEmojiCategory(const EmojiCategory *category) { auto posToGo = category->mapToParent(QPoint()).y(); - auto current = scroll_area_->verticalScrollBar()->value(); + auto current = scrollArea_->verticalScrollBar()->value(); if (current == posToGo) return; @@ -228,10 +218,10 @@ void EmojiPanel::showEmojiCategory(const EmojiCategory *category) // To ensure the category is at the top, we move to the top // and go as normal to the next category. if (current > posToGo) - this->scroll_area_->ensureVisible(0, 0, 0, 0); + this->scrollArea_->ensureVisible(0, 0, 0, 0); posToGo += 6 * 50; - this->scroll_area_->ensureVisible(0, posToGo, 0, 0); + this->scrollArea_->ensureVisible(0, posToGo, 0, 0); } void EmojiPanel::leaveEvent(QEvent *event) @@ -241,6 +231,23 @@ void EmojiPanel::leaveEvent(QEvent *event) fadeOut(); } +void EmojiPanel::paintEvent(QPaintEvent *event) +{ + QPainter p(this); + DropShadow::draw(p, + shadowMargin_, + 4.0, + QColor(120, 120, 120, 92), + QColor(255, 255, 255, 0), + 0.0, + 1.0, + 0.6, + width(), + height()); + + QWidget::paintEvent(event); +} + void EmojiPanel::fadeOut() { animation_->setDirection(QAbstractAnimation::Forward); diff --git a/src/Login.cc b/src/Login.cc index 1f1fb572..9c584685 100644 --- a/src/Login.cc +++ b/src/Login.cc @@ -34,10 +34,22 @@ LoginRequest::LoginRequest(QString username, QString password) QByteArray LoginRequest::serialize() noexcept { +#if defined(Q_OS_MAC) + QString initialDeviceName("nheko on Mac OS"); +#elif defined(Q_OS_LINUX) + QString initialDeviceName("nheko on Linux"); +#elif defined(Q_OS_WIN) + QString initialDeviceName("nheko on Windows"); +#else + QString initialDeviceName("nheko"); +#endif + QJsonObject body{ {"type", "m.login.password"}, {"user", user_}, - {"password", password_}}; + {"password", password_}, + {"initial_device_display_name", initialDeviceName}, + }; return QJsonDocument(body).toJson(QJsonDocument::Compact); } diff --git a/src/LoginPage.cc b/src/LoginPage.cc index 4514b607..4329baad 100644 --- a/src/LoginPage.cc +++ b/src/LoginPage.cc @@ -37,12 +37,10 @@ LoginPage::LoginPage(QSharedPointer client, QWidget *parent) back_button_ = new FlatButton(this); back_button_->setMinimumSize(QSize(30, 30)); back_button_->setForegroundColor("#333333"); - back_button_->setCursor(QCursor(Qt::PointingHandCursor)); advanced_settings_button_ = new FlatButton(this); advanced_settings_button_->setMinimumSize(QSize(30, 30)); advanced_settings_button_->setForegroundColor("#333333"); - advanced_settings_button_->setCursor(QCursor(Qt::PointingHandCursor)); QIcon icon; icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); @@ -105,7 +103,6 @@ LoginPage::LoginPage(QSharedPointer client, QWidget *parent) login_button_->setBackgroundColor(QColor("#333333")); login_button_->setForegroundColor(QColor("white")); login_button_->setMinimumSize(350, 65); - login_button_->setCursor(QCursor(Qt::PointingHandCursor)); login_button_->setFontSize(17); login_button_->setCornerRadius(3); diff --git a/src/LogoutDialog.cc b/src/LogoutDialog.cc new file mode 100644 index 00000000..b8d55449 --- /dev/null +++ b/src/LogoutDialog.cc @@ -0,0 +1,57 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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 . + */ + +#include +#include + +#include "LogoutDialog.h" +#include "Theme.h" + +LogoutDialog::LogoutDialog(QWidget *parent) + : QFrame(parent) +{ + setMaximumSize(400, 400); + setStyleSheet("background-color: #f9f9f9"); + + auto layout = new QVBoxLayout(this); + layout->setSpacing(30); + layout->setMargin(20); + + auto buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(0); + buttonLayout->setMargin(0); + + confirmBtn_ = new FlatButton("OK", this); + confirmBtn_->setFontSize(12); + + cancelBtn_ = new FlatButton(tr("CANCEL"), this); + cancelBtn_->setFontSize(12); + + buttonLayout->addStretch(1); + buttonLayout->addWidget(confirmBtn_); + buttonLayout->addWidget(cancelBtn_); + + auto label = new QLabel(tr("Logout. Are you sure?"), this); + label->setFont(QFont("Open Sans", 14)); + 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/MainWindow.cc b/src/MainWindow.cc index ce7ca206..783ad5ff 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -22,6 +22,8 @@ #include #include +MainWindow *MainWindow::instance_ = nullptr; + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , progress_modal_{nullptr} @@ -148,6 +150,8 @@ void MainWindow::showChatPage(QString userid, QString homeserver, QString token) login_page_->reset(); chat_page_->bootstrap(userid, homeserver, token); + + instance_ = this; } void MainWindow::showWelcomePage() @@ -204,6 +208,11 @@ bool MainWindow::hasActiveUser() settings.contains("auth/user_id"); } +MainWindow *MainWindow::instance() +{ + return instance_; +} + MainWindow::~MainWindow() { } diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index a605623f..927db541 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -287,6 +287,29 @@ void MatrixClient::onRoomAvatarResponse(QNetworkReply *reply) emit roomAvatarRetrieved(roomid, pixmap); } +void MatrixClient::onUserAvatarResponse(QNetworkReply *reply) +{ + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } + + auto data = reply->readAll(); + + if (data.size() == 0) + return; + + auto roomid = reply->property("userid").toString(); + + QImage img; + img.loadFromData(data); + + emit userAvatarRetrieved(roomid, img); +} void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply) { reply->deleteLater(); @@ -392,6 +415,9 @@ void MatrixClient::onResponse(QNetworkReply *reply) case Endpoint::RoomAvatar: onRoomAvatarResponse(reply); break; + case Endpoint::UserAvatar: + onUserAvatarResponse(reply); + break; case Endpoint::GetOwnAvatar: onGetOwnAvatarResponse(reply); break; @@ -591,6 +617,32 @@ void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url reply->setProperty("endpoint", static_cast(Endpoint::RoomAvatar)); } +void MatrixClient::fetchUserAvatar(const QString &userId, const QUrl &avatarUrl) +{ + QList url_parts = avatarUrl.toString().split("mxc://"); + + if (url_parts.size() != 2) { + qDebug() << "Invalid format for user avatar " << avatarUrl.toString(); + return; + } + + QUrlQuery query; + query.addQueryItem("width", "128"); + query.addQueryItem("height", "128"); + 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); + reply->setProperty("userid", userId); + reply->setProperty("endpoint", static_cast(Endpoint::UserAvatar)); +} + void MatrixClient::downloadImage(const QString &event_id, const QUrl &url) { QNetworkRequest image_request(url); diff --git a/src/RegisterPage.cc b/src/RegisterPage.cc index 33df0b62..867ac7f1 100644 --- a/src/RegisterPage.cc +++ b/src/RegisterPage.cc @@ -35,7 +35,6 @@ RegisterPage::RegisterPage(QSharedPointer client, QWidget *parent) back_button_ = new FlatButton(this); back_button_->setMinimumSize(QSize(30, 30)); - back_button_->setCursor(QCursor(Qt::PointingHandCursor)); QIcon icon; icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); @@ -109,7 +108,6 @@ RegisterPage::RegisterPage(QSharedPointer client, QWidget *parent) register_button_->setBackgroundColor(QColor("#333333")); register_button_->setForegroundColor(QColor("white")); register_button_->setMinimumSize(350, 65); - register_button_->setCursor(QCursor(Qt::PointingHandCursor)); register_button_->setFontSize(17); register_button_->setCornerRadius(3); diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc index 7753536e..3d4c5355 100644 --- a/src/RoomInfoListItem.cc +++ b/src/RoomInfoListItem.cc @@ -24,10 +24,14 @@ #include "RoomState.h" #include "Theme.h" -RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *parent) +RoomInfoListItem::RoomInfoListItem(QSharedPointer settings, + RoomState state, + QString room_id, + QWidget *parent) : QWidget(parent) , state_(state) , roomId_(room_id) + , roomSettings_{settings} , isPressed_(false) , maxHeight_(IconSize + 2 * Padding) , unreadMsgCount_(0) @@ -44,6 +48,24 @@ RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *pa ripple_overlay_ = new RippleOverlay(this); ripple_overlay_->setClipPath(path); ripple_overlay_->setClipping(true); + + menu_ = new Menu(this); + + toggleNotifications_ = new QAction(notificationText(), this); + + connect(toggleNotifications_, &QAction::triggered, this, [=]() { + roomSettings_->toggleNotifications(); + }); + + menu_->addAction(toggleNotifications_); +} + +QString RoomInfoListItem::notificationText() +{ + if (roomSettings_.isNull() || roomSettings_->isNotificationsEnabled()) + return QString(tr("Disable notifications")); + + return tr("Enable notifications"); } void RoomInfoListItem::paintEvent(QPaintEvent *event) @@ -193,8 +215,21 @@ void RoomInfoListItem::setState(const RoomState &new_state) repaint(); } +void RoomInfoListItem::contextMenuEvent(QContextMenuEvent *event) +{ + Q_UNUSED(event); + + toggleNotifications_->setText(notificationText()); + menu_->popup(event->globalPos()); +} + void RoomInfoListItem::mousePressEvent(QMouseEvent *event) { + if (event->buttons() == Qt::RightButton) { + QWidget::mousePressEvent(event); + return; + } + emit clicked(roomId_); setPressedState(true); diff --git a/src/RoomList.cc b/src/RoomList.cc index 6d0e185b..55c71b19 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc @@ -92,10 +92,17 @@ void RoomList::calculateUnreadMessageCount() emit totalUnreadMessageCountUpdated(total_unread_msgs); } -void RoomList::setInitialRooms(const QMap &states) +void RoomList::setInitialRooms(const QMap> &settings, + const QMap &states) { rooms_.clear(); + if (settings.size() != states.size()) { + qWarning() << "Initializing room list"; + qWarning() << "Different number of room states and room settings"; + return; + } + for (auto it = states.constBegin(); it != states.constEnd(); it++) { auto room_id = it.key(); auto state = it.value(); @@ -103,7 +110,7 @@ void RoomList::setInitialRooms(const QMap &states) if (!state.getAvatar().toString().isEmpty()) client_->fetchRoomAvatar(room_id, state.getAvatar()); - RoomInfoListItem *room_item = new RoomInfoListItem(state, room_id, scrollArea_); + RoomInfoListItem *room_item = new RoomInfoListItem(settings[room_id], state, room_id, scrollArea_); connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom); rooms_.insert(room_id, QSharedPointer(room_item)); diff --git a/src/TextInputWidget.cc b/src/TextInputWidget.cc index cda00c2c..82cc8b4e 100644 --- a/src/TextInputWidget.cc +++ b/src/TextInputWidget.cc @@ -25,6 +25,7 @@ FilteredTextEdit::FilteredTextEdit(QWidget *parent) : QTextEdit(parent) { + setAcceptRichText(false); } void FilteredTextEdit::keyPressEvent(QKeyEvent *event) @@ -49,7 +50,6 @@ TextInputWidget::TextInputWidget(QWidget *parent) top_layout_->setMargin(0); send_file_button_ = new FlatButton(this); - send_file_button_->setCursor(Qt::PointingHandCursor); QIcon send_file_icon; send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); @@ -63,7 +63,6 @@ TextInputWidget::TextInputWidget(QWidget *parent) input_->setStyleSheet("color: #333333; font-size: 13px; border-radius: 0; padding-top: 10px;"); send_message_button_ = new FlatButton(this); - send_message_button_->setCursor(Qt::PointingHandCursor); send_message_button_->setForegroundColor(QColor("#acc7dc")); QIcon send_message_icon; @@ -72,7 +71,6 @@ TextInputWidget::TextInputWidget(QWidget *parent) send_message_button_->setIconSize(QSize(24, 24)); emoji_button_ = new EmojiPickButton(this); - emoji_button_->setCursor(Qt::PointingHandCursor); emoji_button_->setForegroundColor(QColor("#acc7dc")); QIcon emoji_icon; diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc index b1c58e6a..cf8d5e85 100644 --- a/src/TimelineItem.cc +++ b/src/TimelineItem.cc @@ -17,8 +17,10 @@ #include #include +#include #include +#include "AvatarProvider.h" #include "ImageItem.h" #include "TimelineItem.h" #include "TimelineViewManager.h" @@ -29,65 +31,119 @@ static const QString URL_HTML = "\\1setContentsMargins(7, 0, 0, 0); + topLayout_->setSpacing(9); + + topLayout_->addLayout(sideLayout_); + topLayout_->addLayout(mainLayout_, 1); +} + TimelineItem::TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent) : QWidget(parent) { + init(); + body.replace(URL_REGEX, URL_HTML); + auto displayName = TimelineViewManager::displayName(userid); generateTimestamp(QDateTime::currentDateTime()); - generateBody(TimelineViewManager::displayName(userid), color, body); - setupLayout(); + generateBody(displayName, color, body); + + setupAvatarLayout(displayName); + + mainLayout_->addLayout(headerLayout_); + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(0); + + AvatarProvider::resolve(userid, this); } TimelineItem::TimelineItem(QString body, QWidget *parent) : QWidget(parent) { + init(); + body.replace(URL_REGEX, URL_HTML); generateTimestamp(QDateTime::currentDateTime()); generateBody(body); - setupLayout(); + + setupSimpleLayout(); + + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(2); } TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent &event, const QString &color, QWidget *parent) : QWidget(parent) { + init(); + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); + auto displayName = TimelineViewManager::displayName(event.sender()); + generateTimestamp(timestamp); - generateBody(TimelineViewManager::displayName(event.sender()), color, ""); + generateBody(displayName, color, ""); - top_layout_ = new QHBoxLayout(); - top_layout_->setMargin(0); - top_layout_->addWidget(time_label_); + setupAvatarLayout(displayName); - auto right_layout = new QVBoxLayout(); - right_layout->addWidget(content_label_); - right_layout->addWidget(image); + auto imageLayout = new QHBoxLayout(); + imageLayout->addWidget(image); + imageLayout->addStretch(1); - top_layout_->addLayout(right_layout); - top_layout_->addStretch(1); + mainLayout_->addLayout(headerLayout_); + mainLayout_->addLayout(imageLayout); + mainLayout_->setContentsMargins(0, 4, 0, 0); + mainLayout_->setSpacing(0); - setLayout(top_layout_); + AvatarProvider::resolve(event.sender(), this); } TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent &event, QWidget *parent) : QWidget(parent) { + init(); + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); generateTimestamp(timestamp); - top_layout_ = new QHBoxLayout(); - top_layout_->setMargin(0); - top_layout_->addWidget(time_label_); - top_layout_->addWidget(image, 1); - top_layout_->addStretch(1); + setupSimpleLayout(); - setLayout(top_layout_); + auto imageLayout = new QHBoxLayout(); + imageLayout->setMargin(0); + imageLayout->addWidget(image); + imageLayout->addStretch(1); + + mainLayout_->addLayout(imageLayout); + mainLayout_->setContentsMargins(0, 4, 0, 0); + mainLayout_->setSpacing(2); } TimelineItem::TimelineItem(const events::MessageEvent &event, bool with_sender, const QString &color, QWidget *parent) : QWidget(parent) { + init(); + auto body = event.content().body().trimmed().toHtmlEscaped(); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); @@ -96,17 +152,34 @@ TimelineItem::TimelineItem(const events::MessageEvent &event, bool body.replace(URL_REGEX, URL_HTML); body = "" + body + ""; - if (with_sender) - generateBody(TimelineViewManager::displayName(event.sender()), color, body); - else + if (with_sender) { + auto displayName = TimelineViewManager::displayName(event.sender()); + + generateBody(displayName, color, body); + setupAvatarLayout(displayName); + + mainLayout_->addLayout(headerLayout_); + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(0); + + AvatarProvider::resolve(event.sender(), this); + } else { generateBody(body); - setupLayout(); + setupSimpleLayout(); + + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(2); + } } TimelineItem::TimelineItem(const events::MessageEvent &event, bool with_sender, const QString &color, QWidget *parent) : QWidget(parent) { + init(); + auto body = event.content().body().trimmed().toHtmlEscaped(); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); @@ -114,34 +187,45 @@ TimelineItem::TimelineItem(const events::MessageEvent &event, bool w body.replace(URL_REGEX, URL_HTML); - if (with_sender) - generateBody(TimelineViewManager::displayName(event.sender()), color, body); - else + if (with_sender) { + auto displayName = TimelineViewManager::displayName(event.sender()); + generateBody(displayName, color, body); + + setupAvatarLayout(displayName); + + mainLayout_->addLayout(headerLayout_); + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(0); + + AvatarProvider::resolve(event.sender(), this); + } else { generateBody(body); - setupLayout(); + setupSimpleLayout(); + + mainLayout_->addWidget(body_); + mainLayout_->setMargin(0); + mainLayout_->setSpacing(2); + } } +// Only the body is displayed. void TimelineItem::generateBody(const QString &body) { - content_label_ = new QLabel(this); - content_label_->setWordWrap(true); - content_label_->setAlignment(Qt::AlignTop); - content_label_->setStyleSheet("margin: 0;"); - QString content( - "" - "" - "" - " " - " %1" - " " - "" - ""); - content_label_->setText(content.arg(replaceEmoji(body))); - content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); - content_label_->setOpenExternalLinks(true); + QString content("%1"); + + body_ = new QLabel(this); + body_->setWordWrap(true); + body_->setFont(bodyFont_); + body_->setText(content.arg(replaceEmoji(body))); + body_->setAlignment(Qt::AlignTop); + + body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); + body_->setOpenExternalLinks(true); } +// The username/timestamp is displayed along with the message body. void TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body) { auto sender = userid; @@ -150,64 +234,35 @@ void TimelineItem::generateBody(const QString &userid, const QString &color, con if (userid.split(":")[0].split("@").size() > 1) sender = userid.split(":")[0].split("@")[1]; - content_label_ = new QLabel(this); - content_label_->setWordWrap(true); - content_label_->setAlignment(Qt::AlignTop); - content_label_->setStyleSheet("margin: 0;"); - QString content( - "" - "" - "" - " " - " %2" - " " - " " - " %3" - " " - "" - ""); - content_label_->setText(content.arg(color).arg(sender).arg(replaceEmoji(body))); - content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); - content_label_->setOpenExternalLinks(true); + QString userContent(" %2 "); + QString bodyContent(" %1 "); + + userName_ = new QLabel(this); + userName_->setFont(usernameFont_); + userName_->setText(userContent.arg(color).arg(sender)); + userName_->setAlignment(Qt::AlignTop); + + if (body.isEmpty()) + return; + + body_ = new QLabel(this); + body_->setFont(bodyFont_); + body_->setWordWrap(true); + body_->setAlignment(Qt::AlignTop); + body_->setText(bodyContent.arg(replaceEmoji(body))); + body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); + body_->setOpenExternalLinks(true); } void TimelineItem::generateTimestamp(const QDateTime &time) { - auto local_time = time.toString("HH:mm"); + QString msg(" %1 "); - time_label_ = new QLabel(this); - QString msg( - "" - "" - "" - " " - " %1" - " " - "" - ""); - time_label_->setText(msg.arg(local_time)); - time_label_->setStyleSheet("margin-left: 7px; margin-right: 7px; margin-top: 0;"); - time_label_->setAlignment(Qt::AlignTop); -} - -void TimelineItem::setupLayout() -{ - if (time_label_ == nullptr) { - qWarning() << "TimelineItem: Time label is not initialized"; - return; - } - - if (content_label_ == nullptr) { - qWarning() << "TimelineItem: Content label is not initialized"; - return; - } - - top_layout_ = new QHBoxLayout(); - top_layout_->setMargin(0); - top_layout_->addWidget(time_label_); - top_layout_->addWidget(content_label_, 1); - - setLayout(top_layout_); + timestamp_ = new QLabel(this); + timestamp_->setFont(timestampFont_); + timestamp_->setText(msg.arg(time.toString("HH:mm"))); + timestamp_->setAlignment(Qt::AlignTop); + timestamp_->setStyleSheet("margin-top: 2px;"); } QString TimelineItem::replaceEmoji(const QString &body) @@ -227,6 +282,46 @@ QString TimelineItem::replaceEmoji(const QString &body) return fmtBody; } +void TimelineItem::setupAvatarLayout(const QString &userName) +{ + topLayout_->setContentsMargins(7, 6, 0, 0); + + userAvatar_ = new Avatar(this); + userAvatar_->setLetter(QChar(userName[0]).toUpper()); + userAvatar_->setBackgroundColor(QColor("#eee")); + userAvatar_->setTextColor(QColor("black")); + userAvatar_->setSize(32); + + // TODO: The provided user name should be a UserId class + if (userName[0] == '@' && userName.size() > 1) + userAvatar_->setLetter(QChar(userName[1]).toUpper()); + + sideLayout_->addWidget(userAvatar_); + sideLayout_->addStretch(1); + sideLayout_->setMargin(0); + sideLayout_->setSpacing(0); + + headerLayout_->addWidget(userName_); + headerLayout_->addWidget(timestamp_, 1); + headerLayout_->setMargin(0); +} + +void TimelineItem::setupSimpleLayout() +{ + sideLayout_->addWidget(timestamp_); + sideLayout_->addStretch(1); + + topLayout_->setContentsMargins(9, 0, 0, 0); +} + +void TimelineItem::setUserAvatar(const QImage &avatar) +{ + if (userAvatar_ == nullptr) + return; + + userAvatar_->setImage(avatar); +} + TimelineItem::~TimelineItem() { } diff --git a/src/TimelineView.cc b/src/TimelineView.cc index dbea0ad4..5cd59fe5 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -213,6 +213,9 @@ int TimelineView::addEvents(const Timeline &timeline) { int message_count = 0; + QSettings settings; + QString localUser = settings.value("auth/user_id").toString(); + if (isInitialSync) { prev_batch_token_ = timeline.previousBatch(); isInitialSync = false; @@ -220,10 +223,13 @@ int TimelineView::addEvents(const Timeline &timeline) for (const auto &event : timeline.events()) { TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom); + auto sender = event.toObject().value("sender").toString(); if (item != nullptr) { - message_count += 1; addTimelineItem(item, TimelineDirection::Bottom); + + if (sender != localUser) + message_count += 1; } } diff --git a/src/TopRoomBar.cc b/src/TopRoomBar.cc index b35291cb..4b4c8aa5 100644 --- a/src/TopRoomBar.cc +++ b/src/TopRoomBar.cc @@ -21,6 +21,7 @@ TopRoomBar::TopRoomBar(QWidget *parent) : QWidget(parent) + , buttonSize_{32} { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setMinimumSize(QSize(0, 65)); @@ -28,7 +29,7 @@ TopRoomBar::TopRoomBar(QWidget *parent) top_layout_ = new QHBoxLayout(); top_layout_->setSpacing(10); - top_layout_->setContentsMargins(10, 10, 0, 10); + top_layout_->setMargin(10); avatar_ = new Avatar(this); avatar_->setLetter(QChar('?')); @@ -49,31 +50,40 @@ TopRoomBar::TopRoomBar(QWidget *parent) text_layout_->addWidget(name_label_); text_layout_->addWidget(topic_label_); - settings_button_ = new FlatButton(this); - settings_button_->setForegroundColor(QColor("#acc7dc")); - settings_button_->setCursor(QCursor(Qt::PointingHandCursor)); - settings_button_->setStyleSheet("width: 30px; height: 30px;"); + settingsBtn_ = new FlatButton(this); + settingsBtn_->setForegroundColor(QColor("#acc7dc")); + settingsBtn_->setFixedSize(buttonSize_, buttonSize_); + settingsBtn_->setCornerRadius(buttonSize_ / 2); QIcon settings_icon; - settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off); - settings_button_->setIcon(settings_icon); - settings_button_->setIconSize(QSize(16, 16)); - - search_button_ = new FlatButton(this); - search_button_->setForegroundColor(QColor("#acc7dc")); - search_button_->setCursor(QCursor(Qt::PointingHandCursor)); - search_button_->setStyleSheet("width: 30px; height: 30px;"); - - QIcon search_icon; - search_icon.addFile(":/icons/icons/search.png", QSize(), QIcon::Normal, QIcon::Off); - search_button_->setIcon(search_icon); - search_button_->setIconSize(QSize(16, 16)); + settings_icon.addFile(":/icons/icons/vertical-ellipsis.png", QSize(), QIcon::Normal, QIcon::Off); + settingsBtn_->setIcon(settings_icon); + settingsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2)); top_layout_->addWidget(avatar_); top_layout_->addLayout(text_layout_); top_layout_->addStretch(1); - top_layout_->addWidget(search_button_); - top_layout_->addWidget(settings_button_); + top_layout_->addWidget(settingsBtn_); + + menu_ = new Menu(this); + + toggleNotifications_ = new QAction(tr("Disable notifications"), this); + connect(toggleNotifications_, &QAction::triggered, this, [=]() { + roomSettings_->toggleNotifications(); + }); + + menu_->addAction(toggleNotifications_); + + connect(settingsBtn_, &QPushButton::clicked, this, [=]() { + if (roomSettings_->isNotificationsEnabled()) + toggleNotifications_->setText(tr("Disable notifications")); + else + toggleNotifications_->setText(tr("Enable notifications")); + + auto pos = mapToGlobal(settingsBtn_->pos()); + menu_->popup(QPoint(pos.x() + buttonSize_ - menu_->sizeHint().width(), + pos.y() + buttonSize_)); + }); setLayout(top_layout_); } @@ -106,6 +116,11 @@ void TopRoomBar::paintEvent(QPaintEvent *event) style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this); } +void TopRoomBar::setRoomSettings(QSharedPointer settings) +{ + roomSettings_ = settings; +} + TopRoomBar::~TopRoomBar() { } diff --git a/src/UserInfoWidget.cc b/src/UserInfoWidget.cc index 2fca8925..361689ef 100644 --- a/src/UserInfoWidget.cc +++ b/src/UserInfoWidget.cc @@ -16,14 +16,19 @@ */ #include +#include #include "FlatButton.h" +#include "MainWindow.h" #include "UserInfoWidget.h" UserInfoWidget::UserInfoWidget(QWidget *parent) : QWidget(parent) , display_name_("User") , user_id_("@user:homeserver.org") + , logoutModal_{nullptr} + , logoutDialog_{nullptr} + , logoutButtonSize_{32} { QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); setSizePolicy(sizePolicy); @@ -72,19 +77,46 @@ UserInfoWidget::UserInfoWidget(QWidget *parent) logoutButton_ = new FlatButton(this); logoutButton_->setForegroundColor(QColor("#555459")); - logoutButton_->setCursor(QCursor(Qt::PointingHandCursor)); + logoutButton_->setFixedSize(logoutButtonSize_, logoutButtonSize_); + logoutButton_->setCornerRadius(logoutButtonSize_ / 2); QIcon icon; icon.addFile(":/icons/icons/power-button-off.png", QSize(), QIcon::Normal, QIcon::Off); logoutButton_->setIcon(icon); - logoutButton_->setIconSize(QSize(16, 16)); + logoutButton_->setIconSize(QSize(logoutButtonSize_ / 2, logoutButtonSize_ / 2)); buttonLayout_->addWidget(logoutButton_); topLayout_->addLayout(buttonLayout_); - connect(logoutButton_, SIGNAL(clicked()), this, SIGNAL(logout())); + // Show the confirmation dialog. + connect(logoutButton_, &QPushButton::clicked, this, [=]() { + if (logoutDialog_ == nullptr) { + logoutDialog_ = new LogoutDialog(this); + connect(logoutDialog_, SIGNAL(closing(bool)), this, SLOT(closeLogoutDialog(bool))); + } + + if (logoutModal_ == nullptr) { + logoutModal_ = new OverlayModal(MainWindow::instance(), logoutDialog_); + logoutModal_->setDuration(100); + logoutModal_->setColor(QColor(55, 55, 55, 170)); + } + + logoutModal_->fadeIn(); + }); +} + +void UserInfoWidget::closeLogoutDialog(bool isLoggingOut) +{ + logoutModal_->fadeOut(); + + if (isLoggingOut) { + // Waiting for the modal to fade out. + QTimer::singleShot(100, this, [=]() { + emit logout(); + }); + } } UserInfoWidget::~UserInfoWidget() diff --git a/src/WelcomePage.cc b/src/WelcomePage.cc index b88c4db1..67d64120 100644 --- a/src/WelcomePage.cc +++ b/src/WelcomePage.cc @@ -61,7 +61,6 @@ WelcomePage::WelcomePage(QWidget *parent) register_button_->setBackgroundColor(QColor("#333333")); register_button_->setForegroundColor(QColor("white")); register_button_->setMinimumSize(240, 60); - register_button_->setCursor(QCursor(Qt::PointingHandCursor)); register_button_->setFontSize(14); register_button_->setCornerRadius(3); @@ -69,7 +68,6 @@ WelcomePage::WelcomePage(QWidget *parent) login_button_->setBackgroundColor(QColor("#333333")); login_button_->setForegroundColor(QColor("white")); login_button_->setMinimumSize(240, 60); - login_button_->setCursor(QCursor(Qt::PointingHandCursor)); login_button_->setFontSize(14); login_button_->setCornerRadius(3); diff --git a/src/main.cc b/src/main.cc index e6d4c4e7..bf165cab 100644 --- a/src/main.cc +++ b/src/main.cc @@ -36,9 +36,7 @@ int main(int argc, char *argv[]) QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-BoldItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Semibold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-SemiboldItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf"); app.setWindowIcon(QIcon(":/logos/nheko.png")); diff --git a/src/ui/FlatButton.cc b/src/ui/FlatButton.cc index a4f42a5d..183b2294 100644 --- a/src/ui/FlatButton.cc +++ b/src/ui/FlatButton.cc @@ -29,6 +29,7 @@ void FlatButton::init() setStyle(&ThemeManager::instance()); setAttribute(Qt::WA_Hover); setMouseTracking(true); + setCursor(QCursor(Qt::PointingHandCursor)); QPainterPath path; path.addRoundedRect(rect(), corner_radius_, corner_radius_); @@ -336,7 +337,7 @@ void FlatButton::mousePressEvent(QMouseEvent *event) ripple->setOpacityStartValue(0.35); ripple->setColor(foregroundColor()); ripple->radiusAnimation()->setDuration(250); - ripple->opacityAnimation()->setDuration(400); + ripple->opacityAnimation()->setDuration(250); ripple_overlay_->addRipple(ripple); }