Merge remote-tracking branch 'origin/master'

This commit is contained in:
RiotTranslate 2017-06-05 10:32:21 +00:00
commit 914afb131d
37 changed files with 1060 additions and 295 deletions

View File

@ -8,6 +8,13 @@ find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED) find_package(Qt5Network REQUIRED)
find_package(Qt5LinguistTools 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_C_COMPILER gcc)
set(CMAKE_CXX_STANDARD 11) 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") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
-std=c++11 \
-Wall \ -Wall \
-Wextra \ -Wextra \
-Werror \ -Werror \
@ -72,6 +78,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
endif() endif()
set(SRC_FILES set(SRC_FILES
src/AvatarProvider.cc
src/ChatPage.cc src/ChatPage.cc
src/Deserializable.cc src/Deserializable.cc
src/EmojiCategory.cc src/EmojiCategory.cc
@ -88,6 +95,7 @@ set(SRC_FILES
src/Login.cc src/Login.cc
src/LoginPage.cc src/LoginPage.cc
src/LoginSettings.cc src/LoginSettings.cc
src/LogoutDialog.cc
src/MainWindow.cc src/MainWindow.cc
src/MatrixClient.cc src/MatrixClient.cc
src/Profile.cc src/Profile.cc
@ -153,6 +161,7 @@ include_directories(include/events)
include_directories(include/events/messages) include_directories(include/events/messages)
qt5_wrap_cpp(MOC_HEADERS qt5_wrap_cpp(MOC_HEADERS
include/AvatarProvider.h
include/ChatPage.h include/ChatPage.h
include/EmojiCategory.h include/EmojiCategory.h
include/EmojiItemDelegate.h include/EmojiItemDelegate.h
@ -165,6 +174,7 @@ qt5_wrap_cpp(MOC_HEADERS
include/TimelineViewManager.h include/TimelineViewManager.h
include/LoginPage.h include/LoginPage.h
include/LoginSettings.h include/LoginSettings.h
include/LogoutDialog.h
include/MainWindow.h include/MainWindow.h
include/MatrixClient.h include/MatrixClient.h
include/RegisterPage.h include/RegisterPage.h

View File

@ -1,6 +1,6 @@
nheko 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 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. feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client.

47
include/AvatarProvider.h Normal file
View File

@ -0,0 +1,47 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QImage>
#include <QObject>
#include <QSharedPointer>
#include <QUrl>
#include "MatrixClient.h"
#include "TimelineItem.h"
class AvatarProvider : public QObject
{
Q_OBJECT
public:
static void init(QSharedPointer<MatrixClient> 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<MatrixClient> client_;
static QMap<QString, QList<TimelineItem *>> toBeResolved_;
static QMap<QString, QImage> userAvatars_;
static QMap<QString, QUrl> avatarUrls_;
};

View File

@ -23,6 +23,7 @@
#include "MatrixClient.h" #include "MatrixClient.h"
#include "RoomList.h" #include "RoomList.h"
#include "RoomSettings.h"
#include "RoomState.h" #include "RoomState.h"
#include "Splitter.h" #include "Splitter.h"
#include "TextInputWidget.h" #include "TextInputWidget.h"
@ -92,6 +93,7 @@ private:
UserInfoWidget *user_info_widget_; UserInfoWidget *user_info_widget_;
QMap<QString, RoomState> state_manager_; QMap<QString, RoomState> state_manager_;
QMap<QString, QSharedPointer<RoomSettings>> settingsManager_;
// Matrix Client API provider. // Matrix Client API provider.
QSharedPointer<MatrixClient> client_; QSharedPointer<MatrixClient> client_;

View File

@ -17,9 +17,7 @@
#pragma once #pragma once
#include <QFrame>
#include <QGraphicsOpacityEffect> #include <QGraphicsOpacityEffect>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QScrollArea> #include <QScrollArea>
#include <QWidget> #include <QWidget>
@ -27,7 +25,7 @@
#include "EmojiCategory.h" #include "EmojiCategory.h"
#include "EmojiProvider.h" #include "EmojiProvider.h"
class EmojiPanel : public QFrame class EmojiPanel : public QWidget
{ {
Q_OBJECT Q_OBJECT
@ -43,25 +41,24 @@ signals:
protected: protected:
void leaveEvent(QEvent *event); void leaveEvent(QEvent *event);
void paintEvent(QPaintEvent *event);
private: private:
void showEmojiCategory(const EmojiCategory *category); void showEmojiCategory(const EmojiCategory *category);
QPropertyAnimation *opacity_anim_; QPropertyAnimation *animation_;
QPropertyAnimation *size_anim_;
QGraphicsOpacityEffect *opacity_; QGraphicsOpacityEffect *opacity_;
QParallelAnimationGroup *animation_;
EmojiProvider emoji_provider_; EmojiProvider emoji_provider_;
QScrollArea *scroll_area_; QScrollArea *scrollArea_;
int shadowMargin_;
// Panel dimensions. // Panel dimensions.
const int WIDTH = 370; int width_;
const int HEIGHT = 350; int height_;
const int ANIMATION_DURATION = 100; int animationDuration_;
const int ANIMATION_OFFSET = 50; int categoryIconSize_;
const int category_icon_size_ = 20;
}; };

36
include/LogoutDialog.h Normal file
View File

@ -0,0 +1,36 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QFrame>
#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_;
};

View File

@ -38,6 +38,8 @@ public:
explicit MainWindow(QWidget *parent = 0); explicit MainWindow(QWidget *parent = 0);
~MainWindow(); ~MainWindow();
static MainWindow *instance();
protected: protected:
void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event);
@ -62,6 +64,8 @@ private slots:
private: private:
bool hasActiveUser(); bool hasActiveUser();
static MainWindow *instance_;
// The initial welcome screen. // The initial welcome screen.
WelcomePage *welcome_page_; WelcomePage *welcome_page_;

View File

@ -41,6 +41,7 @@ public:
void registerUser(const QString &username, const QString &password, const QString &server) noexcept; void registerUser(const QString &username, const QString &password, const QString &server) noexcept;
void versions() noexcept; void versions() noexcept;
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url); void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl);
void fetchOwnAvatar(const QUrl &avatar_url); void fetchOwnAvatar(const QUrl &avatar_url);
void downloadImage(const QString &event_id, const QUrl &url); void downloadImage(const QString &event_id, const QUrl &url);
void messages(const QString &room_id, const QString &from_token) noexcept; 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 registerSuccess(const QString &userid, const QString &homeserver, const QString &token);
void roomAvatarRetrieved(const QString &roomid, const QPixmap &img); void roomAvatarRetrieved(const QString &roomid, const QPixmap &img);
void userAvatarRetrieved(const QString &userId, const QImage &img);
void ownAvatarRetrieved(const QPixmap &img); void ownAvatarRetrieved(const QPixmap &img);
void imageDownloaded(const QString &event_id, const QPixmap &img); void imageDownloaded(const QString &event_id, const QPixmap &img);
@ -95,6 +97,7 @@ private:
Messages, Messages,
Register, Register,
RoomAvatar, RoomAvatar,
UserAvatar,
SendTextMessage, SendTextMessage,
Sync, Sync,
Versions, Versions,
@ -111,6 +114,7 @@ private:
void onInitialSyncResponse(QNetworkReply *reply); void onInitialSyncResponse(QNetworkReply *reply);
void onSyncResponse(QNetworkReply *reply); void onSyncResponse(QNetworkReply *reply);
void onRoomAvatarResponse(QNetworkReply *reply); void onRoomAvatarResponse(QNetworkReply *reply);
void onUserAvatarResponse(QNetworkReply *reply);
void onImageResponse(QNetworkReply *reply); void onImageResponse(QNetworkReply *reply);
void onMessagesResponse(QNetworkReply *reply); void onMessagesResponse(QNetworkReply *reply);

View File

@ -17,9 +17,13 @@
#pragma once #pragma once
#include <QAction>
#include <QSharedPointer>
#include <QWidget> #include <QWidget>
#include "Menu.h"
#include "RippleOverlay.h" #include "RippleOverlay.h"
#include "RoomSettings.h"
#include "RoomState.h" #include "RoomState.h"
class RoomInfoListItem : public QWidget class RoomInfoListItem : public QWidget
@ -27,7 +31,11 @@ class RoomInfoListItem : public QWidget
Q_OBJECT Q_OBJECT
public: public:
RoomInfoListItem(RoomState state, QString room_id, QWidget *parent = 0); RoomInfoListItem(QSharedPointer<RoomSettings> settings,
RoomState state,
QString room_id,
QWidget *parent = 0);
~RoomInfoListItem(); ~RoomInfoListItem();
void updateUnreadMessageCount(int count); void updateUnreadMessageCount(int count);
@ -48,8 +56,11 @@ public slots:
protected: protected:
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
private: private:
QString notificationText();
const int Padding = 7; const int Padding = 7;
const int IconSize = 46; const int IconSize = 46;
@ -64,6 +75,11 @@ private:
QPixmap roomAvatar_; QPixmap roomAvatar_;
Menu *menu_;
QAction *toggleNotifications_;
QSharedPointer<RoomSettings> roomSettings_;
bool isPressed_ = false; bool isPressed_ = false;
int maxHeight_; int maxHeight_;

View File

@ -35,7 +35,8 @@ public:
RoomList(QSharedPointer<MatrixClient> client, QWidget *parent = 0); RoomList(QSharedPointer<MatrixClient> client, QWidget *parent = 0);
~RoomList(); ~RoomList();
void setInitialRooms(const QMap<QString, RoomState> &states); void setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &settings,
const QMap<QString, RoomState> &states);
void sync(const QMap<QString, RoomState> &states); void sync(const QMap<QString, RoomState> &states);
void clear(); void clear();

55
include/RoomSettings.h Normal file
View File

@ -0,0 +1,55 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QSettings>
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_;
};

View File

@ -24,6 +24,7 @@
#include "ImageItem.h" #include "ImageItem.h"
#include "Sync.h" #include "Sync.h"
#include "Avatar.h"
#include "Image.h" #include "Image.h"
#include "MessageEvent.h" #include "MessageEvent.h"
#include "Notice.h" #include "Notice.h"
@ -46,19 +47,35 @@ public:
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, const QString &color, QWidget *parent); TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, const QString &color, QWidget *parent);
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent); TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent);
void setUserAvatar(const QImage &pixmap);
~TimelineItem(); ~TimelineItem();
private: private:
void init();
void generateBody(const QString &body); void generateBody(const QString &body);
void generateBody(const QString &userid, const QString &color, const QString &body); void generateBody(const QString &userid, const QString &color, const QString &body);
void generateTimestamp(const QDateTime &time); void generateTimestamp(const QDateTime &time);
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
QString replaceEmoji(const QString &body); 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_; Avatar *userAvatar_;
QLabel *content_label_;
QLabel *timestamp_;
QLabel *userName_;
QLabel *body_;
QFont bodyFont_;
QFont usernameFont_;
QFont timestampFont_;
}; };

View File

@ -17,15 +17,20 @@
#pragma once #pragma once
#include <QAction>
#include <QDebug>
#include <QIcon> #include <QIcon>
#include <QImage> #include <QImage>
#include <QLabel> #include <QLabel>
#include <QPaintEvent> #include <QPaintEvent>
#include <QSharedPointer>
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QWidget> #include <QWidget>
#include "Avatar.h" #include "Avatar.h"
#include "FlatButton.h" #include "FlatButton.h"
#include "Menu.h"
#include "RoomSettings.h"
class TopRoomBar : public QWidget class TopRoomBar : public QWidget
{ {
@ -39,6 +44,7 @@ public:
inline void updateRoomName(const QString &name); inline void updateRoomName(const QString &name);
inline void updateRoomTopic(const QString &topic); inline void updateRoomTopic(const QString &topic);
void updateRoomAvatarFromName(const QString &name); void updateRoomAvatarFromName(const QString &name);
void setRoomSettings(QSharedPointer<RoomSettings> settings);
void reset(); void reset();
@ -52,10 +58,16 @@ private:
QLabel *name_label_; QLabel *name_label_;
QLabel *topic_label_; QLabel *topic_label_;
FlatButton *search_button_; QSharedPointer<RoomSettings> roomSettings_;
FlatButton *settings_button_;
QMenu *menu_;
QAction *toggleNotifications_;
FlatButton *settingsBtn_;
Avatar *avatar_; Avatar *avatar_;
int buttonSize_;
}; };
inline void TopRoomBar::updateRoomAvatar(const QImage &avatar_image) inline void TopRoomBar::updateRoomAvatar(const QImage &avatar_image)

View File

@ -24,6 +24,8 @@
#include "Avatar.h" #include "Avatar.h"
#include "FlatButton.h" #include "FlatButton.h"
#include "LogoutDialog.h"
#include "OverlayModal.h"
class UserInfoWidget : public QWidget class UserInfoWidget : public QWidget
{ {
@ -45,6 +47,9 @@ signals:
protected: protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
private slots:
void closeLogoutDialog(bool isLoggingOut);
private: private:
Avatar *userAvatar_; Avatar *userAvatar_;
@ -62,4 +67,9 @@ private:
QString user_id_; QString user_id_;
QImage avatar_image_; QImage avatar_image_;
OverlayModal *logoutModal_;
LogoutDialog *logoutDialog_;
int logoutButtonSize_;
}; };

100
include/ui/DropShadow.h Normal file
View File

@ -0,0 +1,100 @@
#pragma once
#include <QColor>
#include <QLinearGradient>
#include <QPainter>
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);
}
};

25
include/ui/Menu.h Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <QMenu>
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();
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

View File

@ -22,6 +22,7 @@
<file>icons/emoji-categories/symbols.png</file> <file>icons/emoji-categories/symbols.png</file>
<file>icons/emoji-categories/flags.png</file> <file>icons/emoji-categories/flags.png</file>
<file>icons/vertical-ellipsis.png</file>
</qresource> </qresource>
<qresource prefix="/logos"> <qresource prefix="/logos">

83
src/AvatarProvider.cc Normal file
View File

@ -0,0 +1,83 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AvatarProvider.h"
QSharedPointer<MatrixClient> AvatarProvider::client_;
QMap<QString, QImage> AvatarProvider::userAvatars_;
QMap<QString, QUrl> AvatarProvider::avatarUrls_;
QMap<QString, QList<TimelineItem *>> AvatarProvider::toBeResolved_;
void AvatarProvider::init(QSharedPointer<MatrixClient> 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<TimelineItem *> 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();
}

View File

@ -27,6 +27,7 @@
#include "AliasesEventContent.h" #include "AliasesEventContent.h"
#include "AvatarEventContent.h" #include "AvatarEventContent.h"
#include "AvatarProvider.h"
#include "CanonicalAliasEventContent.h" #include "CanonicalAliasEventContent.h"
#include "CreateEventContent.h" #include "CreateEventContent.h"
#include "HistoryVisibilityEventContent.h" #include "HistoryVisibilityEventContent.h"
@ -127,10 +128,16 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo); connect(room_list_, &RoomList::roomChanged, this, &ChatPage::changeTopRoomInfo);
connect(room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); connect(room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView);
connect(view_manager_, connect(view_manager_, &TimelineViewManager::unreadMessages, this, [=](const QString &roomid, int count) {
SIGNAL(unreadMessages(const QString &, int)), if (!settingsManager_.contains(roomid)) {
room_list_, qWarning() << "RoomId does not have settings" << roomid;
SLOT(updateUnreadMessageCount(const QString &, int))); room_list_->updateUnreadMessageCount(roomid, count);
return;
}
if (settingsManager_[roomid]->isNotificationsEnabled())
room_list_->updateUnreadMessageCount(roomid, count);
});
connect(room_list_, connect(room_list_,
SIGNAL(totalUnreadMessageCountUpdated(int)), SIGNAL(totalUnreadMessageCountUpdated(int)),
@ -167,17 +174,25 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
SIGNAL(ownAvatarRetrieved(const QPixmap &)), SIGNAL(ownAvatarRetrieved(const QPixmap &)),
this, this,
SLOT(setOwnAvatar(const QPixmap &))); SLOT(setOwnAvatar(const QPixmap &)));
AvatarProvider::init(client);
} }
void ChatPage::logout() void ChatPage::logout()
{ {
sync_timer_->stop(); sync_timer_->stop();
// Delete all config parameters.
QSettings settings; QSettings settings;
settings.remove("auth/access_token"); settings.beginGroup("auth");
settings.remove("auth/home_server"); settings.remove("");
settings.remove("auth/user_id"); settings.endGroup();
settings.remove("client/transaction_id"); settings.beginGroup("client");
settings.remove("");
settings.endGroup();
settings.beginGroup("notifications");
settings.remove("");
settings.endGroup();
// Clear the environment. // Clear the environment.
room_list_->clear(); room_list_->clear();
@ -188,8 +203,11 @@ void ChatPage::logout()
client_->reset(); client_->reset();
state_manager_.clear(); state_manager_.clear();
settingsManager_.clear();
room_avatars_.clear(); room_avatars_.clear();
AvatarProvider::clear();
emit close(); emit close();
} }
@ -286,10 +304,19 @@ void ChatPage::initialSyncCompleted(const SyncResponse &response)
updateDisplayNames(room_state); updateDisplayNames(room_state);
state_manager_.insert(it.key(), room_state); state_manager_.insert(it.key(), room_state);
settingsManager_.insert(it.key(), QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
for (const auto membership : room_state.memberships) {
auto uid = membership.sender();
auto url = membership.content().avatarUrl();
if (!url.toString().isEmpty())
AvatarProvider::setAvatarUrl(uid, url);
}
} }
view_manager_->initialize(response.rooms()); view_manager_->initialize(response.rooms());
room_list_->setInitialRooms(state_manager_); room_list_->setInitialRooms(settingsManager_, state_manager_);
sync_timer_->start(sync_interval_); sync_timer_->start(sync_interval_);
} }
@ -325,6 +352,7 @@ void ChatPage::changeTopRoomInfo(const QString &room_id)
top_bar_->updateRoomName(state.getName()); top_bar_->updateRoomName(state.getName());
top_bar_->updateRoomTopic(state.getTopic()); top_bar_->updateRoomTopic(state.getTopic());
top_bar_->setRoomSettings(settingsManager_[room_id]);
if (room_avatars_.contains(room_id)) if (room_avatars_.contains(room_id))
top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage()); top_bar_->updateRoomAvatar(room_avatars_.value(room_id).toImage());

View File

@ -15,20 +15,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <QDebug>
#include <QLabel>
#include <QPushButton> #include <QPushButton>
#include <QScrollArea> #include <QScrollArea>
#include <QScrollBar> #include <QScrollBar>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "Avatar.h" #include "Avatar.h"
#include "DropShadow.h"
#include "EmojiCategory.h" #include "EmojiCategory.h"
#include "EmojiPanel.h" #include "EmojiPanel.h"
#include "FlatButton.h" #include "FlatButton.h"
EmojiPanel::EmojiPanel(QWidget *parent) EmojiPanel::EmojiPanel(QWidget *parent)
: QFrame(parent) : QWidget(parent)
, shadowMargin_{2}
, width_{370}
, height_{350}
, animationDuration_{100}
, categoryIconSize_{20}
{ {
setStyleSheet( setStyleSheet(
"QWidget {background: #f8fbfe; color: #e8e8e8; border: none;}" "QWidget {background: #f8fbfe; color: #e8e8e8; border: none;}"
@ -40,172 +44,158 @@ EmojiPanel::EmojiPanel(QWidget *parent)
setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_TranslucentBackground, true);
setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip); setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
// TODO: Make it MainWindow aware auto mainWidget = new QWidget(this);
auto main_frame_ = new QFrame(this); mainWidget->setMaximumSize(width_, height_);
main_frame_->setMaximumSize(WIDTH, HEIGHT);
auto top_layout = new QVBoxLayout(this); auto topLayout = new QVBoxLayout(this);
top_layout->addWidget(main_frame_); topLayout->addWidget(mainWidget);
top_layout->setMargin(0); topLayout->setMargin(shadowMargin_);
auto content_layout = new QVBoxLayout(main_frame_); auto contentLayout = new QVBoxLayout(mainWidget);
content_layout->setMargin(0); contentLayout->setMargin(0);
auto emoji_categories = new QFrame(main_frame_); auto emojiCategories = new QFrame(mainWidget);
emoji_categories->setStyleSheet("background-color: #f2f2f2"); emojiCategories->setStyleSheet("background-color: #f2f2f2");
auto categories_layout = new QHBoxLayout(emoji_categories); auto categoriesLayout = new QHBoxLayout(emojiCategories);
categories_layout->setSpacing(6); categoriesLayout->setSpacing(6);
categories_layout->setMargin(5); categoriesLayout->setMargin(5);
auto people_category = new FlatButton(emoji_categories); auto peopleCategory = new FlatButton(emojiCategories);
people_category->setIcon(QIcon(":/icons/icons/emoji-categories/people.png")); peopleCategory->setIcon(QIcon(":/icons/icons/emoji-categories/people.png"));
people_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); peopleCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
people_category->setForegroundColor("gray"); peopleCategory->setForegroundColor("gray");
auto nature_category = new FlatButton(emoji_categories); auto natureCategory_ = new FlatButton(emojiCategories);
nature_category->setIcon(QIcon(":/icons/icons/emoji-categories/nature.png")); natureCategory_->setIcon(QIcon(":/icons/icons/emoji-categories/nature.png"));
nature_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); natureCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
nature_category->setForegroundColor("gray"); natureCategory_->setForegroundColor("gray");
auto food_category = new FlatButton(emoji_categories); auto foodCategory_ = new FlatButton(emojiCategories);
food_category->setIcon(QIcon(":/icons/icons/emoji-categories/foods.png")); foodCategory_->setIcon(QIcon(":/icons/icons/emoji-categories/foods.png"));
food_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); foodCategory_->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
food_category->setForegroundColor("gray"); foodCategory_->setForegroundColor("gray");
auto activity_category = new FlatButton(emoji_categories); auto activityCategory = new FlatButton(emojiCategories);
activity_category->setIcon(QIcon(":/icons/icons/emoji-categories/activity.png")); activityCategory->setIcon(QIcon(":/icons/icons/emoji-categories/activity.png"));
activity_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); activityCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
activity_category->setForegroundColor("gray"); activityCategory->setForegroundColor("gray");
auto travel_category = new FlatButton(emoji_categories); auto travelCategory = new FlatButton(emojiCategories);
travel_category->setIcon(QIcon(":/icons/icons/emoji-categories/travel.png")); travelCategory->setIcon(QIcon(":/icons/icons/emoji-categories/travel.png"));
travel_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); travelCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
travel_category->setForegroundColor("gray"); travelCategory->setForegroundColor("gray");
auto objects_category = new FlatButton(emoji_categories); auto objectsCategory = new FlatButton(emojiCategories);
objects_category->setIcon(QIcon(":/icons/icons/emoji-categories/objects.png")); objectsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/objects.png"));
objects_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); objectsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
objects_category->setForegroundColor("gray"); objectsCategory->setForegroundColor("gray");
auto symbols_category = new FlatButton(emoji_categories); auto symbolsCategory = new FlatButton(emojiCategories);
symbols_category->setIcon(QIcon(":/icons/icons/emoji-categories/symbols.png")); symbolsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/symbols.png"));
symbols_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); symbolsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
symbols_category->setForegroundColor("gray"); symbolsCategory->setForegroundColor("gray");
auto flags_category = new FlatButton(emoji_categories); auto flagsCategory = new FlatButton(emojiCategories);
flags_category->setIcon(QIcon(":/icons/icons/emoji-categories/flags.png")); flagsCategory->setIcon(QIcon(":/icons/icons/emoji-categories/flags.png"));
flags_category->setIconSize(QSize(category_icon_size_, category_icon_size_)); flagsCategory->setIconSize(QSize(categoryIconSize_, categoryIconSize_));
flags_category->setForegroundColor("gray"); flagsCategory->setForegroundColor("gray");
categories_layout->addWidget(people_category); categoriesLayout->addWidget(peopleCategory);
categories_layout->addWidget(nature_category); categoriesLayout->addWidget(natureCategory_);
categories_layout->addWidget(food_category); categoriesLayout->addWidget(foodCategory_);
categories_layout->addWidget(activity_category); categoriesLayout->addWidget(activityCategory);
categories_layout->addWidget(travel_category); categoriesLayout->addWidget(travelCategory);
categories_layout->addWidget(objects_category); categoriesLayout->addWidget(objectsCategory);
categories_layout->addWidget(symbols_category); categoriesLayout->addWidget(symbolsCategory);
categories_layout->addWidget(flags_category); categoriesLayout->addWidget(flagsCategory);
scroll_area_ = new QScrollArea(this); scrollArea_ = new QScrollArea(this);
scroll_area_->setWidgetResizable(true); scrollArea_->setWidgetResizable(true);
scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
auto scroll_widget_ = new QWidget(this); auto scrollWidget = new QWidget(this);
auto scroll_layout_ = new QVBoxLayout(scroll_widget_); auto scrollLayout = new QVBoxLayout(scrollWidget);
scroll_layout_->setMargin(0); scrollLayout->setMargin(0);
scroll_area_->setWidget(scroll_widget_); scrollArea_->setWidget(scrollWidget);
auto people_emoji = new EmojiCategory(tr("Smileys & People"), emoji_provider_.people, scroll_widget_); auto peopleEmoji = new EmojiCategory(tr("Smileys & People"), emoji_provider_.people, scrollWidget);
scroll_layout_->addWidget(people_emoji); scrollLayout->addWidget(peopleEmoji);
auto nature_emoji = new EmojiCategory(tr("Animals & Nature"), emoji_provider_.nature, scroll_widget_); auto natureEmoji = new EmojiCategory(tr("Animals & Nature"), emoji_provider_.nature, scrollWidget);
scroll_layout_->addWidget(nature_emoji); scrollLayout->addWidget(natureEmoji);
auto food_emoji = new EmojiCategory(tr("Food & Drink"), emoji_provider_.food, scroll_widget_); auto foodEmoji = new EmojiCategory(tr("Food & Drink"), emoji_provider_.food, scrollWidget);
scroll_layout_->addWidget(food_emoji); scrollLayout->addWidget(foodEmoji);
auto activity_emoji = new EmojiCategory(tr("Activity"), emoji_provider_.activity, scroll_widget_); auto activityEmoji = new EmojiCategory(tr("Activity"), emoji_provider_.activity, scrollWidget);
scroll_layout_->addWidget(activity_emoji); scrollLayout->addWidget(activityEmoji);
auto travel_emoji = new EmojiCategory(tr("Travel & Places"), emoji_provider_.travel, scroll_widget_); auto travelEmoji = new EmojiCategory(tr("Travel & Places"), emoji_provider_.travel, scrollWidget);
scroll_layout_->addWidget(travel_emoji); scrollLayout->addWidget(travelEmoji);
auto objects_emoji = new EmojiCategory(tr("Objects"), emoji_provider_.objects, scroll_widget_); auto objectsEmoji = new EmojiCategory(tr("Objects"), emoji_provider_.objects, scrollWidget);
scroll_layout_->addWidget(objects_emoji); scrollLayout->addWidget(objectsEmoji);
auto symbols_emoji = new EmojiCategory(tr("Symbols"), emoji_provider_.symbols, scroll_widget_); auto symbolsEmoji = new EmojiCategory(tr("Symbols"), emoji_provider_.symbols, scrollWidget);
scroll_layout_->addWidget(symbols_emoji); scrollLayout->addWidget(symbolsEmoji);
auto flags_emoji = new EmojiCategory(tr("Flags"), emoji_provider_.flags, scroll_widget_); auto flagsEmoji = new EmojiCategory(tr("Flags"), emoji_provider_.flags, scrollWidget);
scroll_layout_->addWidget(flags_emoji); scrollLayout->addWidget(flagsEmoji);
content_layout->addStretch(1); contentLayout->addStretch(1);
content_layout->addWidget(scroll_area_); contentLayout->addWidget(scrollArea_);
content_layout->addWidget(emoji_categories); contentLayout->addWidget(emojiCategories);
setLayout(top_layout);
opacity_ = new QGraphicsOpacityEffect(this); opacity_ = new QGraphicsOpacityEffect(this);
opacity_->setOpacity(1.0); opacity_->setOpacity(1.0);
setGraphicsEffect(opacity_); setGraphicsEffect(opacity_);
opacity_anim_ = new QPropertyAnimation(opacity_, "opacity", this); animation_ = new QPropertyAnimation(opacity_, "opacity", this);
opacity_anim_->setDuration(ANIMATION_DURATION); animation_->setDuration(animationDuration_);
opacity_anim_->setStartValue(1); animation_->setStartValue(1);
opacity_anim_->setEndValue(0); animation_->setEndValue(0);
size_anim_ = new QPropertyAnimation(this); connect(peopleEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
size_anim_->setTargetObject(main_frame_); connect(peopleCategory, &QPushButton::clicked, [this, peopleEmoji]() {
size_anim_->setPropertyName("geometry"); this->showEmojiCategory(peopleEmoji);
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(nature_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(natureEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(nature_category, &QPushButton::clicked, [this, nature_emoji]() { connect(natureCategory_, &QPushButton::clicked, [this, natureEmoji]() {
this->showEmojiCategory(nature_emoji); this->showEmojiCategory(natureEmoji);
}); });
connect(food_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(foodEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(food_category, &QPushButton::clicked, [this, food_emoji]() { connect(foodCategory_, &QPushButton::clicked, [this, foodEmoji]() {
this->showEmojiCategory(food_emoji); this->showEmojiCategory(foodEmoji);
}); });
connect(activity_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(activityEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(activity_category, &QPushButton::clicked, [this, activity_emoji]() { connect(activityCategory, &QPushButton::clicked, [this, activityEmoji]() {
this->showEmojiCategory(activity_emoji); this->showEmojiCategory(activityEmoji);
}); });
connect(travel_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(travelEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(travel_category, &QPushButton::clicked, [this, travel_emoji]() { connect(travelCategory, &QPushButton::clicked, [this, travelEmoji]() {
this->showEmojiCategory(travel_emoji); this->showEmojiCategory(travelEmoji);
}); });
connect(objects_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(objectsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(objects_category, &QPushButton::clicked, [this, objects_emoji]() { connect(objectsCategory, &QPushButton::clicked, [this, objectsEmoji]() {
this->showEmojiCategory(objects_emoji); this->showEmojiCategory(objectsEmoji);
}); });
connect(symbols_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(symbolsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(symbols_category, &QPushButton::clicked, [this, symbols_emoji]() { connect(symbolsCategory, &QPushButton::clicked, [this, symbolsEmoji]() {
this->showEmojiCategory(symbols_emoji); this->showEmojiCategory(symbolsEmoji);
}); });
connect(flags_emoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected); connect(flagsEmoji, &EmojiCategory::emojiSelected, this, &EmojiPanel::emojiSelected);
connect(flags_category, &QPushButton::clicked, [this, flags_emoji]() { connect(flagsCategory, &QPushButton::clicked, [this, flagsEmoji]() {
this->showEmojiCategory(flags_emoji); this->showEmojiCategory(flagsEmoji);
}); });
connect(animation_, &QAbstractAnimation::finished, [this]() { connect(animation_, &QAbstractAnimation::finished, [this]() {
@ -217,7 +207,7 @@ EmojiPanel::EmojiPanel(QWidget *parent)
void EmojiPanel::showEmojiCategory(const EmojiCategory *category) void EmojiPanel::showEmojiCategory(const EmojiCategory *category)
{ {
auto posToGo = category->mapToParent(QPoint()).y(); auto posToGo = category->mapToParent(QPoint()).y();
auto current = scroll_area_->verticalScrollBar()->value(); auto current = scrollArea_->verticalScrollBar()->value();
if (current == posToGo) if (current == posToGo)
return; return;
@ -228,10 +218,10 @@ void EmojiPanel::showEmojiCategory(const EmojiCategory *category)
// To ensure the category is at the top, we move to the top // To ensure the category is at the top, we move to the top
// and go as normal to the next category. // and go as normal to the next category.
if (current > posToGo) if (current > posToGo)
this->scroll_area_->ensureVisible(0, 0, 0, 0); this->scrollArea_->ensureVisible(0, 0, 0, 0);
posToGo += 6 * 50; posToGo += 6 * 50;
this->scroll_area_->ensureVisible(0, posToGo, 0, 0); this->scrollArea_->ensureVisible(0, posToGo, 0, 0);
} }
void EmojiPanel::leaveEvent(QEvent *event) void EmojiPanel::leaveEvent(QEvent *event)
@ -241,6 +231,23 @@ void EmojiPanel::leaveEvent(QEvent *event)
fadeOut(); 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() void EmojiPanel::fadeOut()
{ {
animation_->setDirection(QAbstractAnimation::Forward); animation_->setDirection(QAbstractAnimation::Forward);

View File

@ -34,10 +34,22 @@ LoginRequest::LoginRequest(QString username, QString password)
QByteArray LoginRequest::serialize() noexcept 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{ QJsonObject body{
{"type", "m.login.password"}, {"type", "m.login.password"},
{"user", user_}, {"user", user_},
{"password", password_}}; {"password", password_},
{"initial_device_display_name", initialDeviceName},
};
return QJsonDocument(body).toJson(QJsonDocument::Compact); return QJsonDocument(body).toJson(QJsonDocument::Compact);
} }

View File

@ -37,12 +37,10 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
back_button_ = new FlatButton(this); back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30)); back_button_->setMinimumSize(QSize(30, 30));
back_button_->setForegroundColor("#333333"); back_button_->setForegroundColor("#333333");
back_button_->setCursor(QCursor(Qt::PointingHandCursor));
advanced_settings_button_ = new FlatButton(this); advanced_settings_button_ = new FlatButton(this);
advanced_settings_button_->setMinimumSize(QSize(30, 30)); advanced_settings_button_->setMinimumSize(QSize(30, 30));
advanced_settings_button_->setForegroundColor("#333333"); advanced_settings_button_->setForegroundColor("#333333");
advanced_settings_button_->setCursor(QCursor(Qt::PointingHandCursor));
QIcon icon; QIcon icon;
icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
@ -105,7 +103,6 @@ LoginPage::LoginPage(QSharedPointer<MatrixClient> client, QWidget *parent)
login_button_->setBackgroundColor(QColor("#333333")); login_button_->setBackgroundColor(QColor("#333333"));
login_button_->setForegroundColor(QColor("white")); login_button_->setForegroundColor(QColor("white"));
login_button_->setMinimumSize(350, 65); login_button_->setMinimumSize(350, 65);
login_button_->setCursor(QCursor(Qt::PointingHandCursor));
login_button_->setFontSize(17); login_button_->setFontSize(17);
login_button_->setCornerRadius(3); login_button_->setCornerRadius(3);

57
src/LogoutDialog.cc Normal file
View File

@ -0,0 +1,57 @@
/*
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QLabel>
#include <QVBoxLayout>
#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); });
}

View File

@ -22,6 +22,8 @@
#include <QSettings> #include <QSettings>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
MainWindow *MainWindow::instance_ = nullptr;
MainWindow::MainWindow(QWidget *parent) MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) : QMainWindow(parent)
, progress_modal_{nullptr} , progress_modal_{nullptr}
@ -148,6 +150,8 @@ void MainWindow::showChatPage(QString userid, QString homeserver, QString token)
login_page_->reset(); login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token); chat_page_->bootstrap(userid, homeserver, token);
instance_ = this;
} }
void MainWindow::showWelcomePage() void MainWindow::showWelcomePage()
@ -204,6 +208,11 @@ bool MainWindow::hasActiveUser()
settings.contains("auth/user_id"); settings.contains("auth/user_id");
} }
MainWindow *MainWindow::instance()
{
return instance_;
}
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
} }

View File

@ -287,6 +287,29 @@ void MatrixClient::onRoomAvatarResponse(QNetworkReply *reply)
emit roomAvatarRetrieved(roomid, pixmap); 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) void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply)
{ {
reply->deleteLater(); reply->deleteLater();
@ -392,6 +415,9 @@ void MatrixClient::onResponse(QNetworkReply *reply)
case Endpoint::RoomAvatar: case Endpoint::RoomAvatar:
onRoomAvatarResponse(reply); onRoomAvatarResponse(reply);
break; break;
case Endpoint::UserAvatar:
onUserAvatarResponse(reply);
break;
case Endpoint::GetOwnAvatar: case Endpoint::GetOwnAvatar:
onGetOwnAvatarResponse(reply); onGetOwnAvatarResponse(reply);
break; break;
@ -591,6 +617,32 @@ void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url
reply->setProperty("endpoint", static_cast<int>(Endpoint::RoomAvatar)); reply->setProperty("endpoint", static_cast<int>(Endpoint::RoomAvatar));
} }
void MatrixClient::fetchUserAvatar(const QString &userId, const QUrl &avatarUrl)
{
QList<QString> 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<int>(Endpoint::UserAvatar));
}
void MatrixClient::downloadImage(const QString &event_id, const QUrl &url) void MatrixClient::downloadImage(const QString &event_id, const QUrl &url)
{ {
QNetworkRequest image_request(url); QNetworkRequest image_request(url);

View File

@ -35,7 +35,6 @@ RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
back_button_ = new FlatButton(this); back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30)); back_button_->setMinimumSize(QSize(30, 30));
back_button_->setCursor(QCursor(Qt::PointingHandCursor));
QIcon icon; QIcon icon;
icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off); icon.addFile(":/icons/icons/left-angle.png", QSize(), QIcon::Normal, QIcon::Off);
@ -109,7 +108,6 @@ RegisterPage::RegisterPage(QSharedPointer<MatrixClient> client, QWidget *parent)
register_button_->setBackgroundColor(QColor("#333333")); register_button_->setBackgroundColor(QColor("#333333"));
register_button_->setForegroundColor(QColor("white")); register_button_->setForegroundColor(QColor("white"));
register_button_->setMinimumSize(350, 65); register_button_->setMinimumSize(350, 65);
register_button_->setCursor(QCursor(Qt::PointingHandCursor));
register_button_->setFontSize(17); register_button_->setFontSize(17);
register_button_->setCornerRadius(3); register_button_->setCornerRadius(3);

View File

@ -24,10 +24,14 @@
#include "RoomState.h" #include "RoomState.h"
#include "Theme.h" #include "Theme.h"
RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *parent) RoomInfoListItem::RoomInfoListItem(QSharedPointer<RoomSettings> settings,
RoomState state,
QString room_id,
QWidget *parent)
: QWidget(parent) : QWidget(parent)
, state_(state) , state_(state)
, roomId_(room_id) , roomId_(room_id)
, roomSettings_{settings}
, isPressed_(false) , isPressed_(false)
, maxHeight_(IconSize + 2 * Padding) , maxHeight_(IconSize + 2 * Padding)
, unreadMsgCount_(0) , unreadMsgCount_(0)
@ -44,6 +48,24 @@ RoomInfoListItem::RoomInfoListItem(RoomState state, QString room_id, QWidget *pa
ripple_overlay_ = new RippleOverlay(this); ripple_overlay_ = new RippleOverlay(this);
ripple_overlay_->setClipPath(path); ripple_overlay_->setClipPath(path);
ripple_overlay_->setClipping(true); 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) void RoomInfoListItem::paintEvent(QPaintEvent *event)
@ -193,8 +215,21 @@ void RoomInfoListItem::setState(const RoomState &new_state)
repaint(); repaint();
} }
void RoomInfoListItem::contextMenuEvent(QContextMenuEvent *event)
{
Q_UNUSED(event);
toggleNotifications_->setText(notificationText());
menu_->popup(event->globalPos());
}
void RoomInfoListItem::mousePressEvent(QMouseEvent *event) void RoomInfoListItem::mousePressEvent(QMouseEvent *event)
{ {
if (event->buttons() == Qt::RightButton) {
QWidget::mousePressEvent(event);
return;
}
emit clicked(roomId_); emit clicked(roomId_);
setPressedState(true); setPressedState(true);

View File

@ -92,10 +92,17 @@ void RoomList::calculateUnreadMessageCount()
emit totalUnreadMessageCountUpdated(total_unread_msgs); emit totalUnreadMessageCountUpdated(total_unread_msgs);
} }
void RoomList::setInitialRooms(const QMap<QString, RoomState> &states) void RoomList::setInitialRooms(const QMap<QString, QSharedPointer<RoomSettings>> &settings,
const QMap<QString, RoomState> &states)
{ {
rooms_.clear(); 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++) { for (auto it = states.constBegin(); it != states.constEnd(); it++) {
auto room_id = it.key(); auto room_id = it.key();
auto state = it.value(); auto state = it.value();
@ -103,7 +110,7 @@ void RoomList::setInitialRooms(const QMap<QString, RoomState> &states)
if (!state.getAvatar().toString().isEmpty()) if (!state.getAvatar().toString().isEmpty())
client_->fetchRoomAvatar(room_id, state.getAvatar()); 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); connect(room_item, &RoomInfoListItem::clicked, this, &RoomList::highlightSelectedRoom);
rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item)); rooms_.insert(room_id, QSharedPointer<RoomInfoListItem>(room_item));

View File

@ -25,6 +25,7 @@
FilteredTextEdit::FilteredTextEdit(QWidget *parent) FilteredTextEdit::FilteredTextEdit(QWidget *parent)
: QTextEdit(parent) : QTextEdit(parent)
{ {
setAcceptRichText(false);
} }
void FilteredTextEdit::keyPressEvent(QKeyEvent *event) void FilteredTextEdit::keyPressEvent(QKeyEvent *event)
@ -49,7 +50,6 @@ TextInputWidget::TextInputWidget(QWidget *parent)
top_layout_->setMargin(0); top_layout_->setMargin(0);
send_file_button_ = new FlatButton(this); send_file_button_ = new FlatButton(this);
send_file_button_->setCursor(Qt::PointingHandCursor);
QIcon send_file_icon; QIcon send_file_icon;
send_file_icon.addFile(":/icons/icons/clip-dark.png", QSize(), QIcon::Normal, QIcon::Off); 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;"); input_->setStyleSheet("color: #333333; font-size: 13px; border-radius: 0; padding-top: 10px;");
send_message_button_ = new FlatButton(this); send_message_button_ = new FlatButton(this);
send_message_button_->setCursor(Qt::PointingHandCursor);
send_message_button_->setForegroundColor(QColor("#acc7dc")); send_message_button_->setForegroundColor(QColor("#acc7dc"));
QIcon send_message_icon; QIcon send_message_icon;
@ -72,7 +71,6 @@ TextInputWidget::TextInputWidget(QWidget *parent)
send_message_button_->setIconSize(QSize(24, 24)); send_message_button_->setIconSize(QSize(24, 24));
emoji_button_ = new EmojiPickButton(this); emoji_button_ = new EmojiPickButton(this);
emoji_button_->setCursor(Qt::PointingHandCursor);
emoji_button_->setForegroundColor(QColor("#acc7dc")); emoji_button_->setForegroundColor(QColor("#acc7dc"));
QIcon emoji_icon; QIcon emoji_icon;

View File

@ -17,8 +17,10 @@
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
#include <QFontDatabase>
#include <QRegExp> #include <QRegExp>
#include "AvatarProvider.h"
#include "ImageItem.h" #include "ImageItem.h"
#include "TimelineItem.h" #include "TimelineItem.h"
#include "TimelineViewManager.h" #include "TimelineViewManager.h"
@ -29,65 +31,119 @@ static const QString URL_HTML = "<a href=\"\\1\" style=\"color: #333333\">\\1</a
namespace events = matrix::events; namespace events = matrix::events;
namespace msgs = matrix::events::messages; namespace msgs = matrix::events::messages;
void TimelineItem::init()
{
userAvatar_ = nullptr;
timestamp_ = nullptr;
userName_ = nullptr;
body_ = nullptr;
QFontDatabase db;
bodyFont_ = db.font("Open Sans", "Regular", 10);
usernameFont_ = db.font("Open Sans", "Bold", 10);
timestampFont_ = db.font("Open Sans", "Regular", 7);
topLayout_ = new QHBoxLayout(this);
sideLayout_ = new QVBoxLayout();
mainLayout_ = new QVBoxLayout();
headerLayout_ = new QHBoxLayout();
topLayout_->setContentsMargins(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) TimelineItem::TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
auto displayName = TimelineViewManager::displayName(userid);
generateTimestamp(QDateTime::currentDateTime()); generateTimestamp(QDateTime::currentDateTime());
generateBody(TimelineViewManager::displayName(userid), color, body); generateBody(displayName, color, body);
setupLayout();
setupAvatarLayout(displayName);
mainLayout_->addLayout(headerLayout_);
mainLayout_->addWidget(body_);
mainLayout_->setMargin(0);
mainLayout_->setSpacing(0);
AvatarProvider::resolve(userid, this);
} }
TimelineItem::TimelineItem(QString body, QWidget *parent) TimelineItem::TimelineItem(QString body, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
generateTimestamp(QDateTime::currentDateTime()); generateTimestamp(QDateTime::currentDateTime());
generateBody(body); generateBody(body);
setupLayout();
setupSimpleLayout();
mainLayout_->addWidget(body_);
mainLayout_->setMargin(0);
mainLayout_->setSpacing(2);
} }
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, const QString &color, QWidget *parent) TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, const QString &color, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender());
generateTimestamp(timestamp); generateTimestamp(timestamp);
generateBody(TimelineViewManager::displayName(event.sender()), color, ""); generateBody(displayName, color, "");
top_layout_ = new QHBoxLayout(); setupAvatarLayout(displayName);
top_layout_->setMargin(0);
top_layout_->addWidget(time_label_);
auto right_layout = new QVBoxLayout(); auto imageLayout = new QHBoxLayout();
right_layout->addWidget(content_label_); imageLayout->addWidget(image);
right_layout->addWidget(image); imageLayout->addStretch(1);
top_layout_->addLayout(right_layout); mainLayout_->addLayout(headerLayout_);
top_layout_->addStretch(1); 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<msgs::Image> &event, QWidget *parent) TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
generateTimestamp(timestamp); generateTimestamp(timestamp);
top_layout_ = new QHBoxLayout(); setupSimpleLayout();
top_layout_->setMargin(0);
top_layout_->addWidget(time_label_);
top_layout_->addWidget(image, 1);
top_layout_->addStretch(1);
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<msgs::Notice> &event, bool with_sender, const QString &color, QWidget *parent) TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool with_sender, const QString &color, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
auto body = event.content().body().trimmed().toHtmlEscaped(); auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
@ -96,17 +152,34 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
body = "<i style=\"color: #565E5E\">" + body + "</i>"; body = "<i style=\"color: #565E5E\">" + body + "</i>";
if (with_sender) if (with_sender) {
generateBody(TimelineViewManager::displayName(event.sender()), color, body); auto displayName = TimelineViewManager::displayName(event.sender());
else
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); generateBody(body);
setupLayout(); setupSimpleLayout();
mainLayout_->addWidget(body_);
mainLayout_->setMargin(0);
mainLayout_->setSpacing(2);
}
} }
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool with_sender, const QString &color, QWidget *parent) TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool with_sender, const QString &color, QWidget *parent)
: QWidget(parent) : QWidget(parent)
{ {
init();
auto body = event.content().body().trimmed().toHtmlEscaped(); auto body = event.content().body().trimmed().toHtmlEscaped();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
@ -114,34 +187,45 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool w
body.replace(URL_REGEX, URL_HTML); body.replace(URL_REGEX, URL_HTML);
if (with_sender) if (with_sender) {
generateBody(TimelineViewManager::displayName(event.sender()), color, body); auto displayName = TimelineViewManager::displayName(event.sender());
else 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); generateBody(body);
setupLayout(); setupSimpleLayout();
mainLayout_->addWidget(body_);
mainLayout_->setMargin(0);
mainLayout_->setSpacing(2);
}
} }
// Only the body is displayed.
void TimelineItem::generateBody(const QString &body) void TimelineItem::generateBody(const QString &body)
{ {
content_label_ = new QLabel(this); QString content("<span style=\"color: #171919;\">%1</span>");
content_label_->setWordWrap(true);
content_label_->setAlignment(Qt::AlignTop); body_ = new QLabel(this);
content_label_->setStyleSheet("margin: 0;"); body_->setWordWrap(true);
QString content( body_->setFont(bodyFont_);
"<html>" body_->setText(content.arg(replaceEmoji(body)));
"<head/>" body_->setAlignment(Qt::AlignTop);
"<body>"
" <span style=\"font-size: 10pt; color: #171919;\">" body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
" %1" body_->setOpenExternalLinks(true);
" </span>"
"</body>"
"</html>");
content_label_->setText(content.arg(replaceEmoji(body)));
content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
content_label_->setOpenExternalLinks(true);
} }
// The username/timestamp is displayed along with the message body.
void TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body) void TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body)
{ {
auto sender = userid; auto sender = userid;
@ -150,64 +234,35 @@ void TimelineItem::generateBody(const QString &userid, const QString &color, con
if (userid.split(":")[0].split("@").size() > 1) if (userid.split(":")[0].split("@").size() > 1)
sender = userid.split(":")[0].split("@")[1]; sender = userid.split(":")[0].split("@")[1];
content_label_ = new QLabel(this); QString userContent("<span style=\"color: %1\"> %2 </span>");
content_label_->setWordWrap(true); QString bodyContent("<span style=\"color: #171717;\"> %1 </span>");
content_label_->setAlignment(Qt::AlignTop);
content_label_->setStyleSheet("margin: 0;"); userName_ = new QLabel(this);
QString content( userName_->setFont(usernameFont_);
"<html>" userName_->setText(userContent.arg(color).arg(sender));
"<head/>" userName_->setAlignment(Qt::AlignTop);
"<body>"
" <span style=\"font-size: 10pt; font-weight: 600; color: %1\">" if (body.isEmpty())
" %2" return;
" </span>"
" <span style=\"font-size: 10pt; color: #171919;\">" body_ = new QLabel(this);
" %3" body_->setFont(bodyFont_);
" </span>" body_->setWordWrap(true);
"</body>" body_->setAlignment(Qt::AlignTop);
"</html>"); body_->setText(bodyContent.arg(replaceEmoji(body)));
content_label_->setText(content.arg(color).arg(sender).arg(replaceEmoji(body))); body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction); body_->setOpenExternalLinks(true);
content_label_->setOpenExternalLinks(true);
} }
void TimelineItem::generateTimestamp(const QDateTime &time) void TimelineItem::generateTimestamp(const QDateTime &time)
{ {
auto local_time = time.toString("HH:mm"); QString msg("<span style=\"color: #5d6565;\"> %1 </span>");
time_label_ = new QLabel(this); timestamp_ = new QLabel(this);
QString msg( timestamp_->setFont(timestampFont_);
"<html>" timestamp_->setText(msg.arg(time.toString("HH:mm")));
"<head/>" timestamp_->setAlignment(Qt::AlignTop);
"<body>" timestamp_->setStyleSheet("margin-top: 2px;");
" <span style=\"font-size: 7pt; color: #5d6565;\">"
" %1"
" </span>"
"</body>"
"</html>");
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_);
} }
QString TimelineItem::replaceEmoji(const QString &body) QString TimelineItem::replaceEmoji(const QString &body)
@ -227,6 +282,46 @@ QString TimelineItem::replaceEmoji(const QString &body)
return fmtBody; 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() TimelineItem::~TimelineItem()
{ {
} }

View File

@ -213,6 +213,9 @@ int TimelineView::addEvents(const Timeline &timeline)
{ {
int message_count = 0; int message_count = 0;
QSettings settings;
QString localUser = settings.value("auth/user_id").toString();
if (isInitialSync) { if (isInitialSync) {
prev_batch_token_ = timeline.previousBatch(); prev_batch_token_ = timeline.previousBatch();
isInitialSync = false; isInitialSync = false;
@ -220,10 +223,13 @@ int TimelineView::addEvents(const Timeline &timeline)
for (const auto &event : timeline.events()) { for (const auto &event : timeline.events()) {
TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom); TimelineItem *item = parseMessageEvent(event.toObject(), TimelineDirection::Bottom);
auto sender = event.toObject().value("sender").toString();
if (item != nullptr) { if (item != nullptr) {
message_count += 1;
addTimelineItem(item, TimelineDirection::Bottom); addTimelineItem(item, TimelineDirection::Bottom);
if (sender != localUser)
message_count += 1;
} }
} }

View File

@ -21,6 +21,7 @@
TopRoomBar::TopRoomBar(QWidget *parent) TopRoomBar::TopRoomBar(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, buttonSize_{32}
{ {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
setMinimumSize(QSize(0, 65)); setMinimumSize(QSize(0, 65));
@ -28,7 +29,7 @@ TopRoomBar::TopRoomBar(QWidget *parent)
top_layout_ = new QHBoxLayout(); top_layout_ = new QHBoxLayout();
top_layout_->setSpacing(10); top_layout_->setSpacing(10);
top_layout_->setContentsMargins(10, 10, 0, 10); top_layout_->setMargin(10);
avatar_ = new Avatar(this); avatar_ = new Avatar(this);
avatar_->setLetter(QChar('?')); avatar_->setLetter(QChar('?'));
@ -49,31 +50,40 @@ TopRoomBar::TopRoomBar(QWidget *parent)
text_layout_->addWidget(name_label_); text_layout_->addWidget(name_label_);
text_layout_->addWidget(topic_label_); text_layout_->addWidget(topic_label_);
settings_button_ = new FlatButton(this); settingsBtn_ = new FlatButton(this);
settings_button_->setForegroundColor(QColor("#acc7dc")); settingsBtn_->setForegroundColor(QColor("#acc7dc"));
settings_button_->setCursor(QCursor(Qt::PointingHandCursor)); settingsBtn_->setFixedSize(buttonSize_, buttonSize_);
settings_button_->setStyleSheet("width: 30px; height: 30px;"); settingsBtn_->setCornerRadius(buttonSize_ / 2);
QIcon settings_icon; QIcon settings_icon;
settings_icon.addFile(":/icons/icons/cog.png", QSize(), QIcon::Normal, QIcon::Off); settings_icon.addFile(":/icons/icons/vertical-ellipsis.png", QSize(), QIcon::Normal, QIcon::Off);
settings_button_->setIcon(settings_icon); settingsBtn_->setIcon(settings_icon);
settings_button_->setIconSize(QSize(16, 16)); settingsBtn_->setIconSize(QSize(buttonSize_ / 2, buttonSize_ / 2));
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));
top_layout_->addWidget(avatar_); top_layout_->addWidget(avatar_);
top_layout_->addLayout(text_layout_); top_layout_->addLayout(text_layout_);
top_layout_->addStretch(1); top_layout_->addStretch(1);
top_layout_->addWidget(search_button_); top_layout_->addWidget(settingsBtn_);
top_layout_->addWidget(settings_button_);
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_); setLayout(top_layout_);
} }
@ -106,6 +116,11 @@ void TopRoomBar::paintEvent(QPaintEvent *event)
style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this); style()->drawPrimitive(QStyle::PE_Widget, &option, &painter, this);
} }
void TopRoomBar::setRoomSettings(QSharedPointer<RoomSettings> settings)
{
roomSettings_ = settings;
}
TopRoomBar::~TopRoomBar() TopRoomBar::~TopRoomBar()
{ {
} }

View File

@ -16,14 +16,19 @@
*/ */
#include <QDebug> #include <QDebug>
#include <QTimer>
#include "FlatButton.h" #include "FlatButton.h"
#include "MainWindow.h"
#include "UserInfoWidget.h" #include "UserInfoWidget.h"
UserInfoWidget::UserInfoWidget(QWidget *parent) UserInfoWidget::UserInfoWidget(QWidget *parent)
: QWidget(parent) : QWidget(parent)
, display_name_("User") , display_name_("User")
, user_id_("@user:homeserver.org") , user_id_("@user:homeserver.org")
, logoutModal_{nullptr}
, logoutDialog_{nullptr}
, logoutButtonSize_{32}
{ {
QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
setSizePolicy(sizePolicy); setSizePolicy(sizePolicy);
@ -72,19 +77,46 @@ UserInfoWidget::UserInfoWidget(QWidget *parent)
logoutButton_ = new FlatButton(this); logoutButton_ = new FlatButton(this);
logoutButton_->setForegroundColor(QColor("#555459")); logoutButton_->setForegroundColor(QColor("#555459"));
logoutButton_->setCursor(QCursor(Qt::PointingHandCursor)); logoutButton_->setFixedSize(logoutButtonSize_, logoutButtonSize_);
logoutButton_->setCornerRadius(logoutButtonSize_ / 2);
QIcon icon; QIcon icon;
icon.addFile(":/icons/icons/power-button-off.png", QSize(), QIcon::Normal, QIcon::Off); icon.addFile(":/icons/icons/power-button-off.png", QSize(), QIcon::Normal, QIcon::Off);
logoutButton_->setIcon(icon); logoutButton_->setIcon(icon);
logoutButton_->setIconSize(QSize(16, 16)); logoutButton_->setIconSize(QSize(logoutButtonSize_ / 2, logoutButtonSize_ / 2));
buttonLayout_->addWidget(logoutButton_); buttonLayout_->addWidget(logoutButton_);
topLayout_->addLayout(buttonLayout_); 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() UserInfoWidget::~UserInfoWidget()

View File

@ -61,7 +61,6 @@ WelcomePage::WelcomePage(QWidget *parent)
register_button_->setBackgroundColor(QColor("#333333")); register_button_->setBackgroundColor(QColor("#333333"));
register_button_->setForegroundColor(QColor("white")); register_button_->setForegroundColor(QColor("white"));
register_button_->setMinimumSize(240, 60); register_button_->setMinimumSize(240, 60);
register_button_->setCursor(QCursor(Qt::PointingHandCursor));
register_button_->setFontSize(14); register_button_->setFontSize(14);
register_button_->setCornerRadius(3); register_button_->setCornerRadius(3);
@ -69,7 +68,6 @@ WelcomePage::WelcomePage(QWidget *parent)
login_button_->setBackgroundColor(QColor("#333333")); login_button_->setBackgroundColor(QColor("#333333"));
login_button_->setForegroundColor(QColor("white")); login_button_->setForegroundColor(QColor("white"));
login_button_->setMinimumSize(240, 60); login_button_->setMinimumSize(240, 60);
login_button_->setCursor(QCursor(Qt::PointingHandCursor));
login_button_->setFontSize(14); login_button_->setFontSize(14);
login_button_->setCornerRadius(3); login_button_->setCornerRadius(3);

View File

@ -36,9 +36,7 @@ int main(int argc, char *argv[])
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf");
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf");
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.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-Semibold.ttf");
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-SemiboldItalic.ttf");
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf"); QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
app.setWindowIcon(QIcon(":/logos/nheko.png")); app.setWindowIcon(QIcon(":/logos/nheko.png"));

View File

@ -29,6 +29,7 @@ void FlatButton::init()
setStyle(&ThemeManager::instance()); setStyle(&ThemeManager::instance());
setAttribute(Qt::WA_Hover); setAttribute(Qt::WA_Hover);
setMouseTracking(true); setMouseTracking(true);
setCursor(QCursor(Qt::PointingHandCursor));
QPainterPath path; QPainterPath path;
path.addRoundedRect(rect(), corner_radius_, corner_radius_); path.addRoundedRect(rect(), corner_radius_, corner_radius_);
@ -336,7 +337,7 @@ void FlatButton::mousePressEvent(QMouseEvent *event)
ripple->setOpacityStartValue(0.35); ripple->setOpacityStartValue(0.35);
ripple->setColor(foregroundColor()); ripple->setColor(foregroundColor());
ripple->radiusAnimation()->setDuration(250); ripple->radiusAnimation()->setDuration(250);
ripple->opacityAnimation()->setDuration(400); ripple->opacityAnimation()->setDuration(250);
ripple_overlay_->addRipple(ripple); ripple_overlay_->addRipple(ripple);
} }