From 385a7eb00d5fe87ae5c13e1e9ec81626937b8b99 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Fri, 16 Aug 2019 12:32:42 +0300
Subject: [PATCH] Start scheduled compose controls.

---
 .../SourceFiles/chat_helpers/tabbed_panel.cpp |   1 +
 .../SourceFiles/chat_helpers/tabbed_panel.h   |   4 +-
 .../SourceFiles/history/history_widget.cpp    |   7 +-
 Telegram/SourceFiles/history/history_widget.h | 172 ++++++------
 .../view/history_view_compose_controls.cpp    | 251 ++++++++++++++++++
 .../view/history_view_compose_controls.h      | 121 +++++++++
 .../view/history_view_scheduled_section.cpp   |  32 ++-
 .../view/history_view_scheduled_section.h     |   5 +
 .../view/history_view_top_bar_widget.cpp      |  15 +-
 .../view/history_view_top_bar_widget.h        |   1 +
 Telegram/SourceFiles/ui/rp_widget.h           |   5 +
 Telegram/gyp/telegram_sources.txt             |   2 +
 12 files changed, 515 insertions(+), 101 deletions(-)
 create mode 100644 Telegram/SourceFiles/history/view/history_view_compose_controls.cpp
 create mode 100644 Telegram/SourceFiles/history/view/history_view_compose_controls.h

diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
index 897be1926..5a70f2c12 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.cpp
@@ -103,6 +103,7 @@ TabbedPanel::TabbedPanel(
 	setAttribute(Qt::WA_OpaquePaintEvent, false);
 
 	hideChildren();
+	hide();
 }
 
 void TabbedPanel::moveBottomRight(int bottom, int right) {
diff --git a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h
index 7a04003ca..677bf6836 100644
--- a/Telegram/SourceFiles/chat_helpers/tabbed_panel.h
+++ b/Telegram/SourceFiles/chat_helpers/tabbed_panel.h
@@ -25,7 +25,9 @@ class TabbedSelector;
 
 class TabbedPanel : public Ui::RpWidget {
 public:
-	TabbedPanel(QWidget *parent, not_null<Window::SessionController*> controller);
+	TabbedPanel(
+		QWidget *parent,
+		not_null<Window::SessionController*> controller);
 	TabbedPanel(
 		QWidget *parent,
 		not_null<Window::SessionController*> controller,
diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp
index 0ab055df0..b085b91bf 100644
--- a/Telegram/SourceFiles/history/history_widget.cpp
+++ b/Telegram/SourceFiles/history/history_widget.cpp
@@ -443,7 +443,6 @@ HistoryWidget::HistoryWidget(
 	_botKeyboardHide->addClickHandler([=] { toggleKeyboard(); });
 	_botCommandStart->addClickHandler([=] { startBotCommand(); });
 
-	_tabbedPanel->hide();
 	_attachDragDocument->hide();
 	_attachDragPhoto->hide();
 
@@ -3879,8 +3878,7 @@ void HistoryWidget::pushTabbedSelectorToThirdSection(
 
 void HistoryWidget::toggleTabbedSelectorMode() {
 	if (_tabbedPanel) {
-		if (controller()->canShowThirdSection()
-			&& !Adaptive::OneColumn()) {
+		if (controller()->canShowThirdSection() && !Adaptive::OneColumn()) {
 			session().settings().setTabbedSelectorSectionEnabled(true);
 			session().saveSettingsDelayed();
 			pushTabbedSelectorToThirdSection(
@@ -3899,7 +3897,6 @@ void HistoryWidget::returnTabbedSelector(
 		this,
 		controller(),
 		std::move(selector));
-	_tabbedPanel->hide();
 	_tabbedSelectorToggle->installEventFilter(_tabbedPanel);
 	_tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
 	_tabbedSelectorToggleTooltipShown = false;
@@ -3928,7 +3925,7 @@ void HistoryWidget::moveFieldControls() {
 	}
 
 // _attachToggle --------- _inlineResults -------------------------------------- _tabbedPanel --------- _fieldBarCancel
-// (_attachDocument|_attachPhoto) _field (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) [_broadcast] _send
+// (_attachDocument|_attachPhoto) _field (_scheduled) (_silent|_cmdStart|_kbShow) (_kbHide|_tabbedSelectorToggle) _send
 // (_botStart|_unblock|_joinChannel|{_muteUnmute&_discuss})
 
 	auto buttonsBottom = bottom - _attachToggle->height();
diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h
index ac1580e9e..4b023a287 100644
--- a/Telegram/SourceFiles/history/history_widget.h
+++ b/Telegram/SourceFiles/history/history_widget.h
@@ -320,6 +320,41 @@ private:
 	using TabbedPanel = ChatHelpers::TabbedPanel;
 	using TabbedSelector = ChatHelpers::TabbedSelector;
 	using DragState = Storage::MimeDataState;
+	struct BotCallbackInfo {
+		UserData *bot;
+		FullMsgId msgId;
+		int row, col;
+		bool game;
+	};
+	struct PinnedBar {
+		PinnedBar(MsgId msgId, HistoryWidget *parent);
+		~PinnedBar();
+
+		MsgId msgId = 0;
+		HistoryItem *msg = nullptr;
+		Ui::Text::String text;
+		object_ptr<Ui::IconButton> cancel;
+		object_ptr<Ui::PlainShadow> shadow;
+	};
+	enum ScrollChangeType {
+		ScrollChangeNone,
+
+		// When we toggle a pinned message.
+		ScrollChangeAdd,
+
+		// When loading a history part while scrolling down.
+		ScrollChangeNoJumpToBottom,
+	};
+	struct ScrollChange {
+		ScrollChangeType type;
+		int value;
+	};
+	enum class TextUpdateEvent {
+		SaveDraft = (1 << 0),
+		SendTyping = (1 << 1),
+	};
+	using TextUpdateEvents = base::flags<TextUpdateEvent>;
+	friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
 
 	void initTabbedSelector();
 	void updateField();
@@ -488,35 +523,9 @@ private:
 
 	void handlePeerMigration();
 
-	MsgId _replyToId = 0;
-	Ui::Text::String _replyToName;
-	int _replyToNameVersion = 0;
-
-	HistoryItemsList _toForward;
-	Ui::Text::String _toForwardFrom, _toForwardText;
-	int _toForwardNameVersion = 0;
-
-	MsgId _editMsgId = 0;
-
-	HistoryItem *_replyEditMsg = nullptr;
-	Ui::Text::String _replyEditMsgText;
-	mutable base::Timer _updateEditTimeLeftDisplay;
-
-	object_ptr<Ui::IconButton> _fieldBarCancel;
 	void updateReplyEditTexts(bool force = false);
 	void updateReplyEditText(not_null<HistoryItem*> item);
 
-	struct PinnedBar {
-		PinnedBar(MsgId msgId, HistoryWidget *parent);
-		~PinnedBar();
-
-		MsgId msgId = 0;
-		HistoryItem *msg = nullptr;
-		Ui::Text::String text;
-		object_ptr<Ui::IconButton> cancel;
-		object_ptr<Ui::PlainShadow> shadow;
-	};
-	std::unique_ptr<PinnedBar> _pinnedBar;
 	void updatePinnedBar(bool force = false);
 	bool pinnedMsgVisibilityUpdated();
 	void destroyPinnedBar();
@@ -542,7 +551,6 @@ private:
 	// destroys _history and _migrated unread bars
 	void destroyUnreadBar();
 
-	mtpRequestId _saveEditMsgRequestId = 0;
 	void saveEditMsg();
 	void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req);
 	bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req);
@@ -550,49 +558,13 @@ private:
 	void checkPreview();
 	void requestPreview();
 	void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
-
-	QStringList _parsedLinks;
-	QString _previewLinks;
-	WebPageData *_previewData = nullptr;
-	typedef QMap<QString, WebPageId> PreviewCache;
-	PreviewCache _previewCache;
-	mtpRequestId _previewRequest = 0;
-	Ui::Text::String _previewTitle;
-	Ui::Text::String _previewDescription;
-	base::Timer _previewTimer;
-	bool _previewCancelled = false;
-
-	bool _replyForwardPressed = false;
-
-	HistoryItem *_replyReturn = nullptr;
-	QList<MsgId> _replyReturns;
-
 	bool messagesFailed(const RPCError &error, mtpRequestId requestId);
 	void addMessagesToFront(PeerData *peer, const QVector<MTPMessage> &messages);
 	void addMessagesToBack(PeerData *peer, const QVector<MTPMessage> &messages);
 
-	struct BotCallbackInfo {
-		UserData *bot;
-		FullMsgId msgId;
-		int row, col;
-		bool game;
-	};
 	void botCallbackDone(BotCallbackInfo info, const MTPmessages_BotCallbackAnswer &answer, mtpRequestId req);
 	bool botCallbackFail(BotCallbackInfo info, const RPCError &error, mtpRequestId req);
 
-	enum ScrollChangeType {
-		ScrollChangeNone,
-
-		// When we toggle a pinned message.
-		ScrollChangeAdd,
-
-		// When loading a history part while scrolling down.
-		ScrollChangeNoJumpToBottom,
-	};
-	struct ScrollChange {
-		ScrollChangeType type;
-		int value;
-	};
 	void updateHistoryGeometry(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
 	void updateListSize();
 
@@ -613,13 +585,6 @@ private:
 
 	void countHistoryShowFrom();
 
-	enum class TextUpdateEvent {
-		SaveDraft  = (1 << 0),
-		SendTyping = (1 << 1),
-	};
-	using TextUpdateEvents = base::flags<TextUpdateEvent>;
-	friend inline constexpr bool is_flag_type(TextUpdateEvent) { return true; };
-
 	void writeDrafts(Data::Draft **localDraft, Data::Draft **editDraft);
 	void writeDrafts(History *history);
 	void setFieldText(
@@ -650,6 +615,58 @@ private:
 
 	void handleSupportSwitch(not_null<History*> updated);
 
+	void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
+	bool inlineBotResolveFail(QString name, const RPCError &error);
+
+	bool isBotStart() const;
+	bool isBlocked() const;
+	bool isJoinChannel() const;
+	bool isMuteUnmute() const;
+	bool updateCmdStartShown();
+	void updateSendButtonType();
+	bool showRecordButton() const;
+	bool showInlineBotCancel() const;
+	void refreshSilentToggle();
+
+	void setupScheduledToggle();
+	void refreshScheduledToggle();
+
+	MsgId _replyToId = 0;
+	Ui::Text::String _replyToName;
+	int _replyToNameVersion = 0;
+
+	HistoryItemsList _toForward;
+	Ui::Text::String _toForwardFrom, _toForwardText;
+	int _toForwardNameVersion = 0;
+
+	MsgId _editMsgId = 0;
+
+	HistoryItem *_replyEditMsg = nullptr;
+	Ui::Text::String _replyEditMsgText;
+	mutable base::Timer _updateEditTimeLeftDisplay;
+
+	object_ptr<Ui::IconButton> _fieldBarCancel;
+
+	std::unique_ptr<PinnedBar> _pinnedBar;
+
+	mtpRequestId _saveEditMsgRequestId = 0;
+
+	QStringList _parsedLinks;
+	QString _previewLinks;
+	WebPageData *_previewData = nullptr;
+	typedef QMap<QString, WebPageId> PreviewCache;
+	PreviewCache _previewCache;
+	mtpRequestId _previewRequest = 0;
+	Ui::Text::String _previewTitle;
+	Ui::Text::String _previewDescription;
+	base::Timer _previewTimer;
+	bool _previewCancelled = false;
+
+	bool _replyForwardPressed = false;
+
+	HistoryItem *_replyReturn = nullptr;
+	QList<MsgId> _replyReturns;
+
 	PeerData *_peer = nullptr;
 
 	ChannelId _channel = NoChannel;
@@ -699,21 +716,6 @@ private:
 	bool _inlineLookingUpBot = false;
 	mtpRequestId _inlineBotResolveRequestId = 0;
 	bool _isInlineBot = false;
-	void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
-	bool inlineBotResolveFail(QString name, const RPCError &error);
-
-	bool isBotStart() const;
-	bool isBlocked() const;
-	bool isJoinChannel() const;
-	bool isMuteUnmute() const;
-	bool updateCmdStartShown();
-	void updateSendButtonType();
-	bool showRecordButton() const;
-	bool showInlineBotCancel() const;
-	void refreshSilentToggle();
-
-	void setupScheduledToggle();
-	void refreshScheduledToggle();
 
 	std::unique_ptr<HistoryView::ContactStatus> _contactStatus;
 
diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp
new file mode 100644
index 000000000..f57ac36b4
--- /dev/null
+++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.cpp
@@ -0,0 +1,251 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "history/view/history_view_compose_controls.h"
+
+#include "ui/widgets/input_fields.h"
+#include "ui/special_buttons.h"
+#include "lang/lang_keys.h"
+#include "chat_helpers/tabbed_panel.h"
+#include "chat_helpers/tabbed_section.h"
+#include "chat_helpers/tabbed_selector.h"
+#include "chat_helpers/message_field.h"
+#include "chat_helpers/emoji_suggestions_widget.h"
+#include "window/window_session_controller.h"
+#include "inline_bots/inline_results_widget.h"
+#include "styles/style_history.h"
+
+namespace HistoryView {
+
+ComposeControls::ComposeControls(
+	not_null<QWidget*> parent,
+	not_null<Window::SessionController*> window,
+	Mode mode)
+: _parent(parent)
+, _window(window)
+, _mode(mode)
+, _wrap(std::make_unique<Ui::RpWidget>(parent))
+, _send(Ui::CreateChild<Ui::SendButton>(_wrap.get()))
+, _attachToggle(Ui::CreateChild<Ui::IconButton>(
+	_wrap.get(),
+	st::historyAttach))
+, _tabbedSelectorToggle(Ui::CreateChild<Ui::EmojiButton>(
+	_wrap.get(),
+	st::historyAttachEmoji))
+, _field(Ui::CreateChild<Ui::InputField>(
+	_wrap.get(),
+	st::historyComposeField,
+	Ui::InputField::Mode::MultiLine,
+	tr::lng_message_ph()))
+, _tabbedPanel(std::make_unique<ChatHelpers::TabbedPanel>(parent, window))
+, _tabbedSelector(_tabbedPanel->getSelector()) {
+	init();
+}
+
+ComposeControls::~ComposeControls() = default;
+
+Main::Session &ComposeControls::session() const {
+	return _window->session();
+}
+
+void ComposeControls::move(int x, int y) {
+	_wrap->move(x, y);
+}
+
+void ComposeControls::resizeToWidth(int width) {
+	_wrap->resizeToWidth(width);
+	updateHeight();
+}
+
+rpl::producer<int> ComposeControls::height() const {
+	return _wrap->heightValue();
+}
+
+int ComposeControls::heightCurrent() const {
+	return _wrap->height();
+}
+
+void ComposeControls::focus() {
+	_field->setFocus();
+}
+
+rpl::producer<> ComposeControls::cancelRequests() const {
+	return _cancelRequests.events();
+}
+
+void ComposeControls::showStarted() {
+	if (_inlineResults) {
+		_inlineResults->hideFast();
+	}
+	if (_tabbedPanel) {
+		_tabbedPanel->hideFast();
+	}
+	_wrap->hide();
+}
+
+void ComposeControls::showFinished() {
+	if (_inlineResults) {
+		_inlineResults->hideFast();
+	}
+	if (_tabbedPanel) {
+		_tabbedPanel->hideFast();
+	}
+	_wrap->show();
+}
+
+void ComposeControls::showForGrab() {
+	showFinished();
+}
+
+void ComposeControls::init() {
+	initField();
+	initTabbedSelector();
+
+	_wrap->sizeValue(
+	) | rpl::start_with_next([=](QSize size) {
+		updateControlsGeometry(size);
+	}, _wrap->lifetime());
+
+	_wrap->geometryValue(
+	) | rpl::start_with_next([=](QRect rect) {
+		updateOuterGeometry(rect);
+	}, _wrap->lifetime());
+
+	_wrap->paintRequest(
+	) | rpl::start_with_next([=](QRect clip) {
+		paintBackground(clip);
+	}, _wrap->lifetime());
+}
+
+void ComposeControls::initField() {
+	_field->setMaxHeight(st::historyComposeFieldMaxHeight);
+	//Ui::Connect(_field, &Ui::InputField::submitted, [=] { send(); });
+	Ui::Connect(_field, &Ui::InputField::cancelled, [=] { escape(); });
+	//Ui::Connect(_field, &Ui::InputField::tabbed, [=] { fieldTabbed(); });
+	Ui::Connect(_field, &Ui::InputField::resized, [=] { updateHeight(); });
+	//Ui::Connect(_field, &Ui::InputField::focused, [=] { fieldFocused(); });
+	//Ui::Connect(_field, &Ui::InputField::changed, [=] { fieldChanged(); });
+	InitMessageField(_window, _field);
+	const auto suggestions = Ui::Emoji::SuggestionsController::Init(
+		_parent,
+		_field,
+		&_window->session());
+	_raiseEmojiSuggestions = [=] { suggestions->raise(); };
+}
+
+void ComposeControls::initTabbedSelector() {
+	_tabbedSelectorToggle->installEventFilter(_tabbedPanel.get());
+	_tabbedSelectorToggle->addClickHandler([=] {
+		toggleTabbedSelectorMode();
+	});
+}
+
+void ComposeControls::updateControlsGeometry(QSize size) {
+	// _attachToggle -- _inlineResults ------ _tabbedPanel -- _fieldBarCancel
+	// (_attachDocument|_attachPhoto) _field _tabbedSelectorToggle _send
+
+	const auto fieldWidth = size.width()
+		- _attachToggle->width()
+		- st::historySendRight
+		- _send->width()
+		- _tabbedSelectorToggle->width();
+	_field->resizeToWidth(fieldWidth);
+
+	const auto buttonsTop = size.height() - _attachToggle->height();
+
+	auto left = 0;
+	_attachToggle->moveToLeft(left, buttonsTop);
+	left += _attachToggle->width();
+	_field->moveToLeft(
+		left,
+		size.height() - _field->height() - st::historySendPadding);
+
+	auto right = st::historySendRight;
+	_send->moveToRight(right, buttonsTop);
+	right += _send->width();
+	_tabbedSelectorToggle->moveToRight(right, buttonsTop);
+}
+
+void ComposeControls::updateOuterGeometry(QRect rect) {
+	if (_inlineResults) {
+		_inlineResults->moveBottom(rect.y());
+	}
+	if (_tabbedPanel) {
+		_tabbedPanel->moveBottomRight(
+			rect.y() + rect.height() - _attachToggle->height(),
+			rect.x() + rect.width());
+	}
+}
+
+void ComposeControls::paintBackground(QRect clip) {
+	Painter p(_wrap.get());
+
+	p.fillRect(clip, st::historyComposeAreaBg);
+}
+
+void ComposeControls::escape() {
+	_cancelRequests.fire({});
+}
+
+void ComposeControls::pushTabbedSelectorToThirdSection(
+		const Window::SectionShow &params) {
+	if (!_tabbedPanel) {
+		return;
+	//} else if (!_canSendMessages) {
+	//	session().settings().setTabbedReplacedWithInfo(true);
+	//	_window->showPeerInfo(_peer, params.withThirdColumn());
+	//	return;
+	}
+	session().settings().setTabbedReplacedWithInfo(false);
+	_tabbedSelectorToggle->setColorOverrides(
+		&st::historyAttachEmojiActive,
+		&st::historyRecordVoiceFgActive,
+		&st::historyRecordVoiceRippleBgActive);
+	auto destroyingPanel = std::move(_tabbedPanel);
+	auto memento = ChatHelpers::TabbedMemento(
+		destroyingPanel->takeSelector(),
+		crl::guard(_wrap.get(), [=](
+				object_ptr<ChatHelpers::TabbedSelector> selector) {
+			returnTabbedSelector(std::move(selector));
+		}));
+	_window->resizeForThirdSection();
+	_window->showSection(std::move(memento), params.withThirdColumn());
+}
+
+void ComposeControls::toggleTabbedSelectorMode() {
+	if (_tabbedPanel) {
+		if (_window->canShowThirdSection() && !Adaptive::OneColumn()) {
+			session().settings().setTabbedSelectorSectionEnabled(true);
+			session().saveSettingsDelayed();
+			pushTabbedSelectorToThirdSection(
+				Window::SectionShow::Way::ClearStack);
+		} else {
+			_tabbedPanel->toggleAnimated();
+		}
+	} else {
+		_window->closeThirdSection();
+	}
+}
+
+void ComposeControls::returnTabbedSelector(
+		object_ptr<ChatHelpers::TabbedSelector> selector) {
+	_tabbedPanel = std::make_unique<ChatHelpers::TabbedPanel>(
+		_parent,
+		_window,
+		std::move(selector));
+	_tabbedPanel->hide();
+	_tabbedSelectorToggle->installEventFilter(_tabbedPanel.get());
+	_tabbedSelectorToggle->setColorOverrides(nullptr, nullptr, nullptr);
+	updateOuterGeometry(_wrap->geometry());
+}
+
+void ComposeControls::updateHeight() {
+	const auto height = _field->height() + 2 * st::historySendPadding;
+	_wrap->resize(_wrap->width(), height);
+}
+
+} // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/history_view_compose_controls.h
new file mode 100644
index 000000000..e417df1b1
--- /dev/null
+++ b/Telegram/SourceFiles/history/view/history_view_compose_controls.h
@@ -0,0 +1,121 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/unique_qptr.h"
+#include "ui/rp_widget.h"
+#include "ui/effects/animations.h"
+
+namespace ChatHelpers {
+class TabbedPanel;
+class TabbedSelector;
+} // namespace ChatHelpers
+
+namespace InlineBots {
+namespace Layout {
+class ItemBase;
+class Widget;
+} // namespace Layout
+class Result;
+} // namespace InlineBots
+
+namespace Ui {
+class SendButton;
+class IconButton;
+class EmojiButton;
+class InputField;
+} // namespace Ui
+
+namespace Main {
+class Session;
+} // namespace Main
+
+namespace Window {
+class SessionController;
+struct SectionShow;
+} // namespace Window
+
+namespace HistoryView {
+
+class ComposeControls final {
+public:
+	enum class Mode {
+		Normal,
+		Scheduled,
+	};
+
+	ComposeControls(
+		not_null<QWidget*> parent,
+		not_null<Window::SessionController*> window,
+		Mode mode);
+	~ComposeControls();
+
+	[[nodiscard]] Main::Session &session() const;
+
+	void move(int x, int y);
+	void resizeToWidth(int width);
+	[[nodiscard]] rpl::producer<int> height() const;
+	[[nodiscard]] int heightCurrent() const;
+
+	void focus();
+	[[nodiscard]] rpl::producer<> cancelRequests() const;
+
+	void showForGrab();
+	void showStarted();
+	void showFinished();
+
+private:
+	void init();
+	void initField();
+	void initTabbedSelector();
+	void updateHeight();
+	void updateControlsGeometry(QSize size);
+	void updateOuterGeometry(QRect rect);
+	void paintBackground(QRect clip);
+
+	void escape();
+	void toggleTabbedSelectorMode();
+	void pushTabbedSelectorToThirdSection(const Window::SectionShow &params);
+	void returnTabbedSelector(
+		object_ptr<ChatHelpers::TabbedSelector> selector);
+
+	const not_null<QWidget*> _parent;
+	const not_null<Window::SessionController*> _window;
+	Mode _mode = Mode::Normal;
+
+	const std::unique_ptr<Ui::RpWidget> _wrap;
+
+	const not_null<Ui::SendButton*> _send;
+	const not_null<Ui::IconButton*> _attachToggle;
+	const not_null<Ui::EmojiButton*> _tabbedSelectorToggle;
+	const not_null<Ui::InputField*> _field;
+	std::unique_ptr<InlineBots::Layout::Widget> _inlineResults;
+	std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
+	const not_null<ChatHelpers::TabbedSelector*> _tabbedSelector;
+
+	rpl::event_stream<> _cancelRequests;
+
+	bool _recording = false;
+	bool _inField = false;
+	bool _inReplyEditForward = false;
+	bool _inClickable = false;
+	int _recordingSamples = 0;
+	int _recordCancelWidth;
+
+	rpl::lifetime _uploaderSubscriptions;
+
+	// This can animate for a very long time (like in music playing),
+	// so it should be a Basic, not a Simple animation.
+	Ui::Animations::Basic _recordingAnimation;
+	anim::value _recordingLevel;
+
+	Fn<void()> _raiseEmojiSuggestions;
+
+};
+
+} // namespace HistoryView
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
index 348aaf957..1e87e97c2 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "history/view/history_view_scheduled_section.h"
 
+#include "history/view/history_view_compose_controls.h"
 #include "history/view/history_view_top_bar_widget.h"
 #include "history/view/history_view_list_widget.h"
 #include "history/history.h"
@@ -50,6 +51,10 @@ ScheduledWidget::ScheduledWidget(
 , _scroll(this, st::historyScroll, false)
 , _topBar(this, controller)
 , _topBarShadow(this)
+, _composeControls(std::make_unique<ComposeControls>(
+	this,
+	controller,
+	ComposeControls::Mode::Scheduled))
 , _scrollDown(_scroll, st::historyToDown) {
 	_topBar->setActiveChat(_history, TopBarWidget::Section::Scheduled);
 
@@ -83,6 +88,21 @@ ScheduledWidget::ScheduledWidget(
 	connect(_scroll, &Ui::ScrollArea::scrolled, [=] { onScroll(); });
 
 	setupScrollDownButton();
+	setupComposeControls();
+}
+
+ScheduledWidget::~ScheduledWidget() = default;
+
+void ScheduledWidget::setupComposeControls() {
+	_composeControls->height(
+	) | rpl::start_with_next([=] {
+		updateControlsGeometry();
+	}, lifetime());
+
+	_composeControls->cancelRequests(
+	) | rpl::start_with_next([=] {
+		controller()->showBackFromStack();
+	}, lifetime());
 }
 
 void ScheduledWidget::setupScrollDownButton() {
@@ -207,13 +227,14 @@ Dialogs::RowDescriptor ScheduledWidget::activeChat() const {
 QPixmap ScheduledWidget::grabForShowAnimation(const Window::SectionSlideParams &params) {
 	_topBar->updateControlsVisibility();
 	if (params.withTopBarShadow) _topBarShadow->hide();
+	_composeControls->showForGrab();
 	auto result = Ui::GrabWidget(this);
 	if (params.withTopBarShadow) _topBarShadow->show();
 	return result;
 }
 
 void ScheduledWidget::doSetInnerFocus() {
-	_inner->setFocus();
+	_composeControls->focus();
 }
 
 bool ScheduledWidget::showInternal(
@@ -254,6 +275,7 @@ void ScheduledWidget::resizeEvent(QResizeEvent *e) {
 	if (!width() || !height()) {
 		return;
 	}
+	_composeControls->resizeToWidth(width());
 	updateControlsGeometry();
 }
 
@@ -267,9 +289,8 @@ void ScheduledWidget::updateControlsGeometry() {
 	_topBarShadow->resize(contentWidth, st::lineWidth);
 
 	const auto bottom = height();
-	const auto scrollHeight = bottom
-		- _topBar->height();
-//		- _showNext->height();
+	const auto controlsHeight = _composeControls->heightCurrent();
+	const auto scrollHeight = bottom - _topBar->height() - controlsHeight;
 	const auto scrollSize = QSize(contentWidth, scrollHeight);
 	if (_scroll->size() != scrollSize) {
 		_skipScrollEvent = true;
@@ -283,6 +304,7 @@ void ScheduledWidget::updateControlsGeometry() {
 		}
 		updateInnerVisibleArea();
 	}
+	_composeControls->move(0, bottom - controlsHeight);
 
 	updateScrollDownPosition();
 }
@@ -324,10 +346,12 @@ void ScheduledWidget::showAnimatedHook(
 	if (params.withTopBarShadow) {
 		_topBarShadow->show();
 	}
+	_composeControls->showStarted();
 }
 
 void ScheduledWidget::showFinishedHook() {
 	_topBar->setAnimatingMode(false);
+	_composeControls->showFinished();
 }
 
 bool ScheduledWidget::wheelEventFromFloatPlayer(QEvent *e) {
diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
index 2c672fdf1..a64b2b834 100644
--- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
+++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h
@@ -33,6 +33,7 @@ namespace HistoryView {
 class Element;
 class TopBarWidget;
 class ScheduledMemento;
+class ComposeControls;
 
 class ScheduledWidget final
 	: public Window::SectionWidget
@@ -42,6 +43,7 @@ public:
 		QWidget *parent,
 		not_null<Window::SessionController*> controller,
 		not_null<History*> history);
+	~ScheduledWidget();
 
 	not_null<History*> history() const;
 	Dialogs::RowDescriptor activeChat() const override;
@@ -106,6 +108,8 @@ private:
 	void showAtPosition(Data::MessagePosition position);
 	bool showAtPositionNow(Data::MessagePosition position);
 
+	void setupComposeControls();
+
 	void setupScrollDownButton();
 	void scrollDownClicked();
 	void scrollDownAnimationFinish();
@@ -121,6 +125,7 @@ private:
 	QPointer<ListWidget> _inner;
 	object_ptr<TopBarWidget> _topBar;
 	object_ptr<Ui::PlainShadow> _topBarShadow;
+	std::unique_ptr<ComposeControls> _composeControls;
 	bool _skipScrollEvent = false;
 
 	FullMsgId _highlightMessageId;
diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
index 2c147db36..57fb4d6ef 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.cpp
@@ -510,16 +510,21 @@ void TopBarWidget::refreshInfoButton() {
 }
 
 void TopBarWidget::resizeEvent(QResizeEvent *e) {
+	updateSearchVisibility();
 	updateControlsGeometry();
-	const auto smallDialogsColumn = _activeChat.folder()
-		&& (width() < _back->width() + _search->width());
-	_search->setVisible(!smallDialogsColumn);
 }
 
 int TopBarWidget::countSelectedButtonsTop(float64 selectedShown) {
 	return (1. - selectedShown) * (-st::topBarHeight);
 }
 
+void TopBarWidget::updateSearchVisibility() {
+	const auto historyMode = (_section == Section::History);
+	const auto smallDialogsColumn = _activeChat.folder()
+		&& (width() < _back->width() + _search->width());
+	_search->setVisible(historyMode && !smallDialogsColumn);
+}
+
 void TopBarWidget::updateControlsGeometry() {
 	auto hasSelected = (_selectedCount > 0);
 	auto selectedButtonsTop = countSelectedButtonsTop(_selectedShown.value(hasSelected ? 1. : 0.));
@@ -620,9 +625,7 @@ void TopBarWidget::updateControlsVisibility() {
 		_unreadBadge->show();
 	}
 	const auto historyMode = (_section == Section::History);
-	const auto smallDialogsColumn = _activeChat.folder()
-		&& (width() < _back->width() + _search->width());
-	_search->setVisible(historyMode && !smallDialogsColumn);
+	updateSearchVisibility();
 	_menuToggle->setVisible(historyMode && !_activeChat.folder());
 	_infoToggle->setVisible(historyMode
 		&& !_activeChat.folder()
diff --git a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h
index 36bfd95ec..02873bde3 100644
--- a/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h
+++ b/Telegram/SourceFiles/history/view/history_view_top_bar_widget.h
@@ -86,6 +86,7 @@ protected:
 private:
 	void refreshInfoButton();
 	void refreshLang();
+	void updateSearchVisibility();
 	void updateControlsGeometry();
 	void selectedShowCallback();
 	void updateInfoToggleActive();
diff --git a/Telegram/SourceFiles/ui/rp_widget.h b/Telegram/SourceFiles/ui/rp_widget.h
index e7f9e4939..abae25cde 100644
--- a/Telegram/SourceFiles/ui/rp_widget.h
+++ b/Telegram/SourceFiles/ui/rp_widget.h
@@ -77,6 +77,11 @@ inline void DestroyChild(QWidget *child) {
 	delete child;
 }
 
+template <typename ...Args>
+inline auto Connect(Args &&...args) {
+	return QObject::connect(std::forward<Args>(args)...);
+}
+
 void ResizeFitChild(
 	not_null<RpWidget*> parent,
 	not_null<RpWidget*> child);
diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt
index fc3339fd4..ce1e5f570 100644
--- a/Telegram/gyp/telegram_sources.txt
+++ b/Telegram/gyp/telegram_sources.txt
@@ -318,6 +318,8 @@
 <(src_loc)/history/view/media/history_view_wall_paper.cpp
 <(src_loc)/history/view/media/history_view_web_page.h
 <(src_loc)/history/view/media/history_view_web_page.cpp
+<(src_loc)/history/view/history_view_compose_controls.cpp
+<(src_loc)/history/view/history_view_compose_controls.h
 <(src_loc)/history/view/history_view_contact_status.cpp
 <(src_loc)/history/view/history_view_contact_status.h
 <(src_loc)/history/view/history_view_context_menu.cpp