From 5f0ba4830986da7fadfa1da5108e075c9d572733 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 7 Nov 2017 19:12:54 +0400
Subject: [PATCH] Add top bar menu and notifications toggle.

---
 .../dialogs/dialogs_inner_widget.cpp          | 50 ++++------
 .../dialogs/dialogs_inner_widget.h            |  6 +-
 Telegram/SourceFiles/history/history.cpp      |  3 +
 Telegram/SourceFiles/info/info.style          | 36 +++++++-
 .../SourceFiles/info/info_wrap_widget.cpp     | 92 +++++++++++++++++++
 Telegram/SourceFiles/info/info_wrap_widget.h  |  9 ++
 Telegram/SourceFiles/mainwidget.cpp           |  3 -
 .../SourceFiles/window/top_bar_widget.cpp     | 17 +---
 .../SourceFiles/window/window_peer_menu.cpp   | 46 +++++++---
 .../SourceFiles/window/window_peer_menu.h     |  9 +-
 10 files changed, 201 insertions(+), 70 deletions(-)

diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
index 2a1876b0fe..d06d66b43b 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.cpp
@@ -1044,7 +1044,7 @@ void DialogsInner::createDialog(History *history) {
 void DialogsInner::removeDialog(History *history) {
 	if (!history) return;
 	if (history->peer == _menuPeer && _menu) {
-		_menu->deleteLater();
+		InvokeQueued(this, [this] { _menu = nullptr; });
 	}
 	if (_selected && _selected->history() == history) {
 		_selected = nullptr;
@@ -1211,53 +1211,41 @@ void DialogsInner::clearSelection() {
 }
 
 void DialogsInner::contextMenuEvent(QContextMenuEvent *e) {
-	if (_menu) {
-		_menu->deleteLater();
-		_menu = nullptr;
-	}
-	if (_menuPeer) {
-		updateSelectedRow(_menuPeer);
-		_menuPeer = nullptr;
-	}
+	_menu = nullptr;
 
 	if (e->reason() == QContextMenuEvent::Mouse) {
 		_mouseSelection = true;
 		updateSelected();
 	}
 
-	History *history = nullptr;
-	if (_state == DefaultState) {
-		if (_selected) history = _selected->history();
-	} else if (_state == FilteredState || _state == SearchedState) {
-		if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) {
-			history = _filterResults[_filteredSelected]->history();
+	auto history = [&]() -> History* {
+		if (_state == DefaultState) {
+			if (_selected) {
+				return _selected->history();
+			}
+		} else if (_state == FilteredState || _state == SearchedState) {
+			if (_filteredSelected >= 0 && _filteredSelected < _filterResults.size()) {
+				return _filterResults[_filteredSelected]->history();
+			}
 		}
-	}
+		return nullptr;
+	}();
 	if (!history) return;
-	_menuPeer = history->peer;
 
+	_menuPeer = history->peer;
 	if (_pressButton != Qt::LeftButton) {
 		mousePressReleased(_pressButton);
 	}
 
-	_menu = new Ui::PopupMenu(nullptr);
-	Window::PeerMenuOptions options;
-	options.fromChatsList = options.showInfo = true;
+	_menu = base::make_unique_q<Ui::PopupMenu>(nullptr);
 	Window::FillPeerMenu(
 		_controller,
 		_menuPeer,
 		[this](const QString &text, base::lambda<void()> callback) {
 			return _menu->addAction(text, std::move(callback));
 		},
-		options);
-	connect(_menu, SIGNAL(destroyed(QObject*)), this, SLOT(onMenuDestroyed(QObject*)));
-	_menu->popup(e->globalPos());
-	e->accept();
-}
-
-void DialogsInner::onMenuDestroyed(QObject *obj) {
-	if (_menu == obj) {
-		_menu = nullptr;
+		Window::PeerMenuSource::ChatsList);
+	connect(_menu.get(), &QObject::destroyed, [this] {
 		if (_menuPeer) {
 			updateSelectedRow(base::take(_menuPeer));
 		}
@@ -1267,7 +1255,9 @@ void DialogsInner::onMenuDestroyed(QObject *obj) {
 			setMouseTracking(true);
 			updateSelected(localPos);
 		}
-	}
+	});
+	_menu->popup(e->globalPos());
+	e->accept();
 }
 
 void DialogsInner::onParentGeometryChanged() {
diff --git a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
index 0d6398e641..eddb3fcb2d 100644
--- a/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
+++ b/Telegram/SourceFiles/dialogs/dialogs_inner_widget.h
@@ -119,8 +119,6 @@ public slots:
 	void onParentGeometryChanged();
 	void onDialogRowReplaced(Dialogs::Row *oldRow, Dialogs::Row *newRow);
 
-	void onMenuDestroyed(QObject*);
-
 signals:
 	void draggingScrollDelta(int delta);
 	void mustScrollTo(int scrollToTop, int scrollToBottom);
@@ -293,8 +291,8 @@ private:
 	Text _searchFromUserText;
 	PeerData *_menuPeer = nullptr;
 
-	Ui::PopupMenu *_menu = nullptr;
-
 	base::lambda<void()> _loadMoreCallback;
 
+	base::unique_qptr<Ui::PopupMenu> _menu;
+
 };
diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp
index c29142ef1f..b2b81de04b 100644
--- a/Telegram/SourceFiles/history/history.cpp
+++ b/Telegram/SourceFiles/history/history.cpp
@@ -1812,6 +1812,9 @@ void History::setUnreadCount(int newUnreadCount) {
 			Notify::historyMuteUpdated(this);
 		}
 		updateChatListEntry();
+		Notify::peerUpdatedDelayed(
+			peer,
+			Notify::PeerUpdate::Flag::NotificationsEnabled);
 	}
 }
 
diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style
index c454a51ab9..518fb32371 100644
--- a/Telegram/SourceFiles/info/info.style
+++ b/Telegram/SourceFiles/info/info.style
@@ -138,6 +138,24 @@ infoTopBarSearch: IconButton(infoTopBarBack) {
 	icon: icon {{ "top_bar_search", boxTitleCloseFg }};
 	iconOver: icon {{ "top_bar_search", boxTitleCloseFgOver }};
 }
+infoTopBarMenu: IconButton(infoTopBarBack) {
+	width: 48px;
+	icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
+	iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
+	iconPosition: point(18px, -1px);
+	rippleAreaPosition: point(1px, 6px);
+}
+infoTopBarNotifications: IconButton(infoTopBarMenu) {
+	width: 42px;
+	icon: icon {{ "info_notifications", boxTitleCloseFg }};
+	iconOver: icon {{ "info_notifications", boxTitleCloseFgOver }};
+	iconPosition: point(5px, 10px);
+	rippleAreaPosition: point(0px, 6px);
+}
+infoNotificationsActive: icon {{
+	"info_notifications",
+	windowBgActive
+}};
 infoTopBarForward: IconButton(infoTopBarBack) {
 	width: 46px;
 	icon: icon {{ "info_media_forward", boxTitleCloseFg }};
@@ -173,14 +191,27 @@ infoLayerTopBarBack: IconButton(infoTopBarBack) {
 	iconPosition: point(12px, -1px);
 	icon: infoLayerTopBarBackIcon;
 	iconOver: infoLayerTopBarBackIconOver;
-
 	rippleAreaSize: 44px;
 }
 infoLayerTopBarCloseIcon: icon {{ "info_close", boxTitleCloseFg }};
 infoLayerTopBarCloseIconOver: icon {{ "info_close", boxTitleCloseFgOver }};
 infoLayerTopBarClose: IconButton(infoLayerTopBarBack) {
+	width: 50px;
 	icon: infoLayerTopBarCloseIcon;
 	iconOver: infoLayerTopBarCloseIconOver;
+	iconPosition: point(6px, -1px);
+	rippleAreaPosition: point(0px, 6px);
+}
+infoLayerTopBarMenu: IconButton(infoLayerTopBarClose) {
+	width: 44px;
+	icon: icon {{ "title_menu_dots", boxTitleCloseFg }};
+	iconOver: icon {{ "title_menu_dots", boxTitleCloseFgOver }};
+	iconPosition: point(18px, -1px);
+}
+infoLayerTopBarNotifications: IconButton(infoLayerTopBarMenu) {
+	icon: icon {{ "info_notifications", boxTitleCloseFg }};
+	iconOver: icon {{ "info_notifications", boxTitleCloseFgOver }};
+	iconPosition: point(5px, 11px);
 }
 infoLayerTopBarForward: IconButton(infoLayerTopBarBack) {
 	width: 45px;
@@ -207,6 +238,9 @@ infoLayerTopBar: InfoTopBar {
 	searchRow: infoTopBarSearchRow;
 }
 
+infoTopBarMenuPosition: point(-2px, 35px);
+infoLayerTopBarMenuPosition: point(40px, 37px);
+
 infoMinimalWidth: 324px;
 infoDesiredWidth: 430px;
 infoMinimalLayerMargin: 48px;
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.cpp b/Telegram/SourceFiles/info/info_wrap_widget.cpp
index 7430d68661..b0f607d30a 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.cpp
+++ b/Telegram/SourceFiles/info/info_wrap_widget.cpp
@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include <rpl/flatten_latest.h>
 #include <rpl/combine.h>
 #include "info/profile/info_profile_widget.h"
+#include "info/profile/info_profile_values.h"
 #include "info/media/info_media_widget.h"
 #include "info/info_content_widget.h"
 #include "info/info_controller.h"
@@ -32,11 +33,14 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
 #include "ui/widgets/discrete_sliders.h"
 #include "ui/widgets/buttons.h"
 #include "ui/widgets/shadow.h"
+#include "ui/widgets/dropdown_menu.h"
 #include "ui/wrap/fade_wrap.h"
 #include "ui/search_field_controller.h"
 #include "window/window_controller.h"
 #include "window/window_slide_animation.h"
+#include "window/window_peer_menu.h"
 #include "auth_session.h"
+#include "mainwidget.h"
 #include "lang/lang_keys.h"
 #include "styles/style_info.h"
 #include "styles/style_profile.h"
@@ -256,12 +260,100 @@ void WrapWidget::createTopBar() {
 			search,
 			_controller->searchEnabledByContent());
 	}
+	if (_controller->section().type() == Section::Type::Profile) {
+		addProfileMenuButton();
+		addProfileNotificationsButton();
+	}
 
 	_topBar->move(0, 0);
 	_topBar->resizeToWidth(width());
 	_topBar->show();
 }
 
+void WrapWidget::addProfileMenuButton() {
+	Expects(_topBar != nullptr);
+
+	_topBarMenuToggle.reset(_topBar->addButton(
+		base::make_unique_q<Ui::IconButton>(
+			_topBar,
+			(wrap() == Wrap::Layer
+				? st::infoLayerTopBarMenu
+				: st::infoTopBarMenu))));
+	_topBarMenuToggle->addClickHandler([this] {
+		showProfileMenu();
+	});
+}
+
+void WrapWidget::addProfileNotificationsButton() {
+	Expects(_topBar != nullptr);
+
+	auto peer = _controller->peer();
+	auto notifications = _topBar->addButton(
+		base::make_unique_q<Ui::IconButton>(
+			_topBar,
+			(wrap() == Wrap::Layer
+				? st::infoLayerTopBarNotifications
+				: st::infoTopBarNotifications)));
+	notifications->addClickHandler([peer] {
+		App::main()->updateNotifySetting(
+			peer,
+			peer->isMuted()
+				? NotifySettingSetNotify
+				: NotifySettingSetMuted);
+	});
+	Profile::NotificationsEnabledValue(peer)
+		| rpl::start_with_next([notifications](bool enabled) {
+			auto iconOverride = enabled
+				? &st::infoNotificationsActive
+				: nullptr;
+			auto rippleOverride = enabled
+				? &st::lightButtonBgOver
+				: nullptr;
+			notifications->setIconOverride(iconOverride, iconOverride);
+			notifications->setRippleColorOverride(rippleOverride);
+		}, notifications->lifetime());
+}
+
+void WrapWidget::showProfileMenu() {
+	if (_topBarMenu) {
+		_topBarMenu->hideAnimated(
+			Ui::InnerDropdown::HideOption::IgnoreShow);
+		return;
+	}
+	_topBarMenu = base::make_unique_q<Ui::DropdownMenu>(this);
+
+	_topBarMenu->setHiddenCallback([this] {
+		InvokeQueued(this, [this] { _topBarMenu = nullptr; });
+		if (auto toggle = _topBarMenuToggle.get()) {
+			toggle->setForceRippled(false);
+		}
+	});
+	_topBarMenu->setShowStartCallback([this] {
+		if (auto toggle = _topBarMenuToggle.get()) {
+			toggle->setForceRippled(true);
+		}
+	});
+	_topBarMenu->setHideStartCallback([this] {
+		if (auto toggle = _topBarMenuToggle.get()) {
+			toggle->setForceRippled(false);
+		}
+	});
+	_topBarMenuToggle->installEventFilter(_topBarMenu.get());
+
+	Window::FillPeerMenu(
+		_controller->window(),
+		_controller->peer(),
+		[this](const QString &text, base::lambda<void()> callback) {
+			return _topBarMenu->addAction(text, std::move(callback));
+		},
+		Window::PeerMenuSource::Profile);
+	auto position = (wrap() == Wrap::Layer)
+		? st::infoLayerTopBarMenuPosition
+		: st::infoTopBarMenuPosition;
+	_topBarMenu->moveToRight(position.x(), position.y());
+	_topBarMenu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
+}
+
 void WrapWidget::refreshTopBarOverride(SelectedItems &&items) {
 	if (items.list.empty()) {
 		destroyTopBarOverride();
diff --git a/Telegram/SourceFiles/info/info_wrap_widget.h b/Telegram/SourceFiles/info/info_wrap_widget.h
index fb61dfba6b..e15dba0d0f 100644
--- a/Telegram/SourceFiles/info/info_wrap_widget.h
+++ b/Telegram/SourceFiles/info/info_wrap_widget.h
@@ -28,6 +28,8 @@ namespace Ui {
 class SettingsSlider;
 class FadeShadow;
 class PlainShadow;
+class DropdownMenu;
+class IconButton;
 } // namespace Ui
 
 namespace Window {
@@ -181,6 +183,10 @@ private:
 	void destroyTopBarOverride();
 	bool requireTopBarSearch() const;
 
+	void addProfileMenuButton();
+	void addProfileNotificationsButton();
+	void showProfileMenu();
+
 	rpl::variable<Wrap> _wrap;
 	std::unique_ptr<Controller> _controller;
 	object_ptr<ContentWidget> _content = { nullptr };
@@ -190,6 +196,9 @@ private:
 	object_ptr<TopBarOverride> _topBarOverride = { nullptr };
 	Animation _topBarOverrideAnimation;
 	object_ptr<Ui::FadeShadow> _topShadow;
+	base::unique_qptr<Ui::IconButton> _topBarMenuToggle;
+	base::unique_qptr<Ui::DropdownMenu> _topBarMenu;
+
 	Tab _tab = Tab::Profile;
 	std::unique_ptr<ContentMemento> _anotherTabMemento;
 	std::vector<StackItem> _historyStack;
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index dedf0fe7dd..e9e656458f 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -4525,9 +4525,6 @@ void MainWidget::applyNotifySetting(const MTPNotifyPeer &peer, const MTPPeerNoti
 		if (_history->peer() == updatePeer) {
 			_history->updateNotifySettings();
 		}
-		if (changed) {
-			Notify::peerUpdatedDelayed(updatePeer, Notify::PeerUpdate::Flag::NotificationsEnabled);
-		}
 	}
 }
 
diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp
index 418da9548f..684ec2fd3e 100644
--- a/Telegram/SourceFiles/window/top_bar_widget.cpp
+++ b/Telegram/SourceFiles/window/top_bar_widget.cpp
@@ -186,24 +186,13 @@ void TopBarWidget::showMenu() {
 					}
 				}));
 				_menuToggle->installEventFilter(_menu);
-				Window::PeerMenuOptions options;
-				options.showInfo = [&] {
-					if (!Adaptive::ThreeColumn()) {
-						return true;
-					} else if (
-						!Auth().data().thirdSectionInfoEnabled() &&
-						!Auth().data().tabbedReplacedWithInfo()) {
-						return true;
-					}
-					return false;
-				}();
 				Window::FillPeerMenu(
 					_controller,
 					peer,
 					[this](const QString &text, base::lambda<void()> callback) {
 						return _menu->addAction(text, std::move(callback));
 					},
-					options);
+					Window::PeerMenuSource::History);
 				_menu->moveToRight((parentWidget()->width() - width()) + st::topBarMenuPosition.x(), st::topBarMenuPosition.y());
 				_menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
 			}
@@ -512,11 +501,11 @@ void TopBarWidget::updateInfoToggleActive() {
 	auto iconOverride = infoThirdActive
 		? &st::topBarInfoActive
 		: nullptr;
-	auto ripplOverride = infoThirdActive
+	auto rippleOverride = infoThirdActive
 		? &st::lightButtonBgOver
 		: nullptr;
 	_infoToggle->setIconOverride(iconOverride, iconOverride);
-	_infoToggle->setRippleColorOverride(ripplOverride);
+	_infoToggle->setRippleColorOverride(rippleOverride);
 }
 
 Ui::RoundButton *TopBarWidget::mediaTypeButton() {
diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp
index 1b971b8ae3..fa0d31e380 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.cpp
+++ b/Telegram/SourceFiles/window/window_peer_menu.cpp
@@ -65,10 +65,11 @@ public:
 		not_null<Controller*> controller,
 		not_null<PeerData*> peer,
 		const PeerMenuCallback &addAction,
-		const PeerMenuOptions &options);
+		PeerMenuSource source);
 	void fill();
 
 private:
+	bool showInfo();
 	void addPinToggle();
 	void addInfo();
 	void addNotifications();
@@ -81,7 +82,7 @@ private:
 	not_null<Controller*> _controller;
 	not_null<PeerData*> _peer;
 	const PeerMenuCallback &_addAction;
-	const PeerMenuOptions &_options;
+	PeerMenuSource _source;
 
 };
 
@@ -142,11 +143,26 @@ Filler::Filler(
 	not_null<Controller*> controller,
 	not_null<PeerData*> peer,
 	const PeerMenuCallback &addAction,
-	const PeerMenuOptions &options)
+	PeerMenuSource source)
 : _controller(controller)
 , _peer(peer)
 , _addAction(addAction)
-, _options(options) {
+, _source(source) {
+}
+
+bool Filler::showInfo() {
+	if (_source == PeerMenuSource::Profile) {
+		return false;
+	} else if (_controller->historyPeer.current() != _peer) {
+		return true;
+	} else if (!Adaptive::ThreeColumn()) {
+		return true;
+	} else if (
+		!Auth().data().thirdSectionInfoEnabled() &&
+		!Auth().data().tabbedReplacedWithInfo()) {
+		return true;
+	}
+	return false;
 }
 
 void Filler::addPinToggle() {
@@ -294,7 +310,7 @@ void Filler::addBlockUser(not_null<UserData*> user) {
 }
 
 void Filler::addUserActions(not_null<UserData*> user) {
-	if (!_options.fromChatsList) {
+	if (_source != PeerMenuSource::ChatsList) {
 		if (user->isContact()) {
 			_addAction(
 				lang(lng_info_share_contact),
@@ -330,7 +346,7 @@ void Filler::addUserActions(not_null<UserData*> user) {
 }
 
 void Filler::addChatActions(not_null<ChatData*> chat) {
-	if (!_options.fromChatsList) {
+	if (_source != PeerMenuSource::ChatsList) {
 		if (chat->canEdit()) {
 			_addAction(
 				lang(lng_profile_edit_contact),
@@ -360,7 +376,7 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
 }
 
 void Filler::addChannelActions(not_null<ChannelData*> channel) {
-	if (!_options.fromChatsList) {
+	if (_source != PeerMenuSource::ChatsList) {
 		//_addAction(manage);
 		if (channel->canAddMembers()) {
 			_addAction(
@@ -381,7 +397,7 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
 			joinText,
 			[channel] { Auth().api().joinChannel(channel); });
 	}
-	if (!_options.fromChatsList) {
+	if (_source != PeerMenuSource::ChatsList) {
 		auto needReport = !channel->amCreator()
 			&& (!channel->isMegagroup() || channel->isPublic());
 		if (needReport) {
@@ -393,14 +409,16 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
 }
 
 void Filler::fill() {
-	if (_options.fromChatsList) {
+	if (_source == PeerMenuSource::ChatsList) {
 		addPinToggle();
 	}
-	if (_options.showInfo) {
+	if (showInfo()) {
 		addInfo();
 	}
-	addNotifications();
-	if (_options.fromChatsList) {
+	if (_source != PeerMenuSource::Profile) {
+		addNotifications();
+	}
+	if (_source == PeerMenuSource::ChatsList) {
 		addSearch();
 	}
 
@@ -478,8 +496,8 @@ void FillPeerMenu(
 		not_null<Controller*> controller,
 		not_null<PeerData*> peer,
 		const PeerMenuCallback &callback,
-		const PeerMenuOptions &options) {
-	Filler filler(controller, peer, callback, options);
+		PeerMenuSource source) {
+	Filler filler(controller, peer, callback, source);
 	filler.fill();
 }
 
diff --git a/Telegram/SourceFiles/window/window_peer_menu.h b/Telegram/SourceFiles/window/window_peer_menu.h
index bbaebc79d7..acdc15546a 100644
--- a/Telegram/SourceFiles/window/window_peer_menu.h
+++ b/Telegram/SourceFiles/window/window_peer_menu.h
@@ -24,9 +24,10 @@ namespace Window {
 
 class Controller;
 
-struct PeerMenuOptions {
-	bool fromChatsList = false;
-	bool showInfo = false;
+enum class PeerMenuSource {
+	ChatsList,
+	History,
+	Profile,
 };
 
 using PeerMenuCallback = base::lambda<QAction*(
@@ -37,7 +38,7 @@ void FillPeerMenu(
 	not_null<Controller*> controller,
 	not_null<PeerData*> peer,
 	const PeerMenuCallback &addAction,
-	const PeerMenuOptions &options);
+	PeerMenuSource source);
 
 void PeerMenuDeleteContact(not_null<UserData*> user);
 void PeerMenuShareContactBox(not_null<UserData*> user);