Allow schedule of files, stickers, GIFs.
This commit is contained in:
		
							parent
							
								
									77ebdd3576
								
							
						
					
					
						commit
						f690f93f32
					
				
					 8 changed files with 477 additions and 18 deletions
				
			
		| 
						 | 
					@ -129,7 +129,7 @@ template <std::size_t... I>
 | 
				
			||||||
inline void GenericBox::Initer<InitMethod, InitArgs...>::call(
 | 
					inline void GenericBox::Initer<InitMethod, InitArgs...>::call(
 | 
				
			||||||
		not_null<GenericBox*> box,
 | 
							not_null<GenericBox*> box,
 | 
				
			||||||
		std::index_sequence<I...>) {
 | 
							std::index_sequence<I...>) {
 | 
				
			||||||
	std::invoke(method, box, std::get<I>(args)...);
 | 
						std::invoke(method, box, std::get<I>(std::move(args))...);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename InitMethod, typename ...InitArgs>
 | 
					template <typename InitMethod, typename ...InitArgs>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +50,7 @@ inline void CallDelayed(int duration, Guard &&object, Lambda &&lambda) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename Guard, typename Lambda>
 | 
					template <typename Guard, typename Lambda>
 | 
				
			||||||
inline auto LambdaDelayed(int duration, Guard &&object, Lambda &&lambda) {
 | 
					[[nodiscard]] inline auto LambdaDelayed(int duration, Guard &&object, Lambda &&lambda) {
 | 
				
			||||||
	auto guarded = crl::guard(
 | 
						auto guarded = crl::guard(
 | 
				
			||||||
		std::forward<Guard>(object),
 | 
							std::forward<Guard>(object),
 | 
				
			||||||
		std::forward<Lambda>(lambda));
 | 
							std::forward<Lambda>(lambda));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,6 +83,26 @@ rpl::producer<> ComposeControls::sendRequests() const {
 | 
				
			||||||
	return _send->clicks() | rpl::map([] { return rpl::empty_value(); });
 | 
						return _send->clicks() | rpl::map([] { return rpl::empty_value(); });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<> ComposeControls::attachRequests() const {
 | 
				
			||||||
 | 
						return _attachToggle->clicks(
 | 
				
			||||||
 | 
						) | rpl::map([] {
 | 
				
			||||||
 | 
							return rpl::empty_value();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<not_null<DocumentData*>> ComposeControls::fileChosen() const {
 | 
				
			||||||
 | 
						return _fileChosen.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<not_null<PhotoData*>> ComposeControls::photoChosen() const {
 | 
				
			||||||
 | 
						return _photoChosen.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto ComposeControls::inlineResultChosen() const
 | 
				
			||||||
 | 
					->rpl::producer<ChatHelpers::TabbedSelector::InlineChosen> {
 | 
				
			||||||
 | 
						return _inlineResultChosen.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ComposeControls::showStarted() {
 | 
					void ComposeControls::showStarted() {
 | 
				
			||||||
	if (_inlineResults) {
 | 
						if (_inlineResults) {
 | 
				
			||||||
		_inlineResults->hideFast();
 | 
							_inlineResults->hideFast();
 | 
				
			||||||
| 
						 | 
					@ -202,20 +222,13 @@ void ComposeControls::initTabbedSelector() {
 | 
				
			||||||
	}, wrap->lifetime());
 | 
						}, wrap->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	selector->fileChosen(
 | 
						selector->fileChosen(
 | 
				
			||||||
	) | rpl::start_with_next([=](not_null<DocumentData*> document) {
 | 
						) | rpl::start_to_stream(_fileChosen, wrap->lifetime());
 | 
				
			||||||
		//sendExistingDocument(document);
 | 
					 | 
				
			||||||
	}, wrap->lifetime());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	selector->photoChosen(
 | 
						selector->photoChosen(
 | 
				
			||||||
	) | rpl::start_with_next([=](not_null<PhotoData*> photo) {
 | 
						) | rpl::start_to_stream(_photoChosen, wrap->lifetime());
 | 
				
			||||||
		//sendExistingPhoto(photo);
 | 
					 | 
				
			||||||
	}, wrap->lifetime());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	selector->inlineResultChosen(
 | 
						selector->inlineResultChosen(
 | 
				
			||||||
	) | rpl::start_with_next([=](
 | 
						) | rpl::start_to_stream(_inlineResultChosen, wrap->lifetime());
 | 
				
			||||||
			ChatHelpers::TabbedSelector::InlineChosen data) {
 | 
					 | 
				
			||||||
		//sendInlineResult(data.result, data.bot);
 | 
					 | 
				
			||||||
	}, wrap->lifetime());
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ComposeControls::initSendButton() {
 | 
					void ComposeControls::initSendButton() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "base/unique_qptr.h"
 | 
					#include "base/unique_qptr.h"
 | 
				
			||||||
#include "ui/rp_widget.h"
 | 
					#include "ui/rp_widget.h"
 | 
				
			||||||
#include "ui/effects/animations.h"
 | 
					#include "ui/effects/animations.h"
 | 
				
			||||||
 | 
					#include "chat_helpers/tabbed_selector.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace ChatHelpers {
 | 
					namespace ChatHelpers {
 | 
				
			||||||
class TabbedPanel;
 | 
					class TabbedPanel;
 | 
				
			||||||
| 
						 | 
					@ -65,6 +66,11 @@ public:
 | 
				
			||||||
	void focus();
 | 
						void focus();
 | 
				
			||||||
	[[nodiscard]] rpl::producer<> cancelRequests() const;
 | 
						[[nodiscard]] rpl::producer<> cancelRequests() const;
 | 
				
			||||||
	[[nodiscard]] rpl::producer<> sendRequests() const;
 | 
						[[nodiscard]] rpl::producer<> sendRequests() const;
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<> attachRequests() const;
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<not_null<DocumentData*>> fileChosen() const;
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<not_null<PhotoData*>> photoChosen() const;
 | 
				
			||||||
 | 
						[[nodiscard]] auto inlineResultChosen() const
 | 
				
			||||||
 | 
							-> rpl::producer<ChatHelpers::TabbedSelector::InlineChosen>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void pushTabbedSelectorToThirdSection(const Window::SectionShow ¶ms);
 | 
						void pushTabbedSelectorToThirdSection(const Window::SectionShow ¶ms);
 | 
				
			||||||
	bool returnTabbedSelector();
 | 
						bool returnTabbedSelector();
 | 
				
			||||||
| 
						 | 
					@ -108,6 +114,9 @@ private:
 | 
				
			||||||
	std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
 | 
						std::unique_ptr<ChatHelpers::TabbedPanel> _tabbedPanel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	rpl::event_stream<> _cancelRequests;
 | 
						rpl::event_stream<> _cancelRequests;
 | 
				
			||||||
 | 
						rpl::event_stream<not_null<DocumentData*>> _fileChosen;
 | 
				
			||||||
 | 
						rpl::event_stream<not_null<PhotoData*>> _photoChosen;
 | 
				
			||||||
 | 
						rpl::event_stream<ChatHelpers::TabbedSelector::InlineChosen> _inlineResultChosen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool _recording = false;
 | 
						bool _recording = false;
 | 
				
			||||||
	bool _inField = false;
 | 
						bool _inField = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -512,7 +512,7 @@ TimeId DefaultScheduleTime() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ScheduleBox(
 | 
					void ScheduleBox(
 | 
				
			||||||
		not_null<GenericBox*> box,
 | 
							not_null<GenericBox*> box,
 | 
				
			||||||
		Fn<void(Api::SendOptions)> done,
 | 
							FnMut<void(Api::SendOptions)> done,
 | 
				
			||||||
		TimeId time) {
 | 
							TimeId time) {
 | 
				
			||||||
	box->setTitle(tr::lng_schedule_title());
 | 
						box->setTitle(tr::lng_schedule_title());
 | 
				
			||||||
	box->setWidth(st::boxWideWidth);
 | 
						box->setWidth(st::boxWideWidth);
 | 
				
			||||||
| 
						 | 
					@ -582,6 +582,8 @@ void ScheduleBox(
 | 
				
			||||||
		}), (*calendar)->lifetime());
 | 
							}), (*calendar)->lifetime());
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto shared = std::make_shared<FnMut<void(Api::SendOptions)>>(
 | 
				
			||||||
 | 
							std::move(done));
 | 
				
			||||||
	const auto save = [=] {
 | 
						const auto save = [=] {
 | 
				
			||||||
		auto result = Api::SendOptions();
 | 
							auto result = Api::SendOptions();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -602,9 +604,9 @@ void ScheduleBox(
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto copy = done;
 | 
							auto copy = shared;
 | 
				
			||||||
		box->closeBox();
 | 
							box->closeBox();
 | 
				
			||||||
		copy(result);
 | 
							(*copy)(result);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	box->setFocusCallback([=] { timeInput->setFocusFast(); });
 | 
						box->setFocusCallback([=] { timeInput->setFocusFast(); });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ namespace HistoryView {
 | 
				
			||||||
[[nodiscard]] TimeId DefaultScheduleTime();
 | 
					[[nodiscard]] TimeId DefaultScheduleTime();
 | 
				
			||||||
void ScheduleBox(
 | 
					void ScheduleBox(
 | 
				
			||||||
	not_null<GenericBox*> box,
 | 
						not_null<GenericBox*> box,
 | 
				
			||||||
	Fn<void(Api::SendOptions)> done,
 | 
						FnMut<void(Api::SendOptions)> done,
 | 
				
			||||||
	TimeId time);
 | 
						TimeId time);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace HistoryView
 | 
					} // namespace HistoryView
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,22 +15,42 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "history/history_item.h"
 | 
					#include "history/history_item.h"
 | 
				
			||||||
#include "ui/widgets/scroll_area.h"
 | 
					#include "ui/widgets/scroll_area.h"
 | 
				
			||||||
#include "ui/widgets/shadow.h"
 | 
					#include "ui/widgets/shadow.h"
 | 
				
			||||||
 | 
					#include "ui/toast/toast.h"
 | 
				
			||||||
#include "ui/special_buttons.h"
 | 
					#include "ui/special_buttons.h"
 | 
				
			||||||
#include "api/api_common.h"
 | 
					#include "api/api_common.h"
 | 
				
			||||||
 | 
					#include "api/api_sending.h"
 | 
				
			||||||
#include "apiwrap.h"
 | 
					#include "apiwrap.h"
 | 
				
			||||||
#include "boxes/confirm_box.h"
 | 
					#include "boxes/confirm_box.h"
 | 
				
			||||||
 | 
					#include "boxes/send_files_box.h"
 | 
				
			||||||
 | 
					#include "boxes/generic_box.h"
 | 
				
			||||||
#include "window/window_session_controller.h"
 | 
					#include "window/window_session_controller.h"
 | 
				
			||||||
#include "window/window_peer_menu.h"
 | 
					#include "window/window_peer_menu.h"
 | 
				
			||||||
#include "core/event_filter.h"
 | 
					#include "core/event_filter.h"
 | 
				
			||||||
 | 
					#include "core/file_utilities.h"
 | 
				
			||||||
#include "main/main_session.h"
 | 
					#include "main/main_session.h"
 | 
				
			||||||
#include "data/data_session.h"
 | 
					#include "data/data_session.h"
 | 
				
			||||||
#include "data/data_scheduled_messages.h"
 | 
					#include "data/data_scheduled_messages.h"
 | 
				
			||||||
 | 
					#include "storage/storage_media_prepare.h"
 | 
				
			||||||
 | 
					#include "storage/localstorage.h"
 | 
				
			||||||
 | 
					#include "inline_bots/inline_bot_result.h"
 | 
				
			||||||
#include "lang/lang_keys.h"
 | 
					#include "lang/lang_keys.h"
 | 
				
			||||||
#include "styles/style_history.h"
 | 
					#include "styles/style_history.h"
 | 
				
			||||||
#include "styles/style_window.h"
 | 
					#include "styles/style_window.h"
 | 
				
			||||||
#include "styles/style_info.h"
 | 
					#include "styles/style_info.h"
 | 
				
			||||||
 | 
					#include "styles/style_boxes.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace HistoryView {
 | 
					namespace HistoryView {
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ShowErrorToast(const QString &text) {
 | 
				
			||||||
 | 
						auto config = Ui::Toast::Config();
 | 
				
			||||||
 | 
						config.multiline = true;
 | 
				
			||||||
 | 
						config.minWidth = st::msgMinWidth;
 | 
				
			||||||
 | 
						config.text = text;
 | 
				
			||||||
 | 
						Ui::Toast::Show(config);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
 | 
					object_ptr<Window::SectionWidget> ScheduledMemento::createWidget(
 | 
				
			||||||
		QWidget *parent,
 | 
							QWidget *parent,
 | 
				
			||||||
| 
						 | 
					@ -111,6 +131,256 @@ void ScheduledWidget::setupComposeControls() {
 | 
				
			||||||
	) | rpl::start_with_next([=] {
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
		send();
 | 
							send();
 | 
				
			||||||
	}, lifetime());
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->attachRequests(
 | 
				
			||||||
 | 
						) | rpl::filter([=] {
 | 
				
			||||||
 | 
							return !_choosingAttach;
 | 
				
			||||||
 | 
						}) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							_choosingAttach = true;
 | 
				
			||||||
 | 
							App::CallDelayed(
 | 
				
			||||||
 | 
								st::historyAttach.ripple.hideDuration,
 | 
				
			||||||
 | 
								this,
 | 
				
			||||||
 | 
								[=] { _choosingAttach = false; chooseAttach(); });
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->fileChosen(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](not_null<DocumentData*> document) {
 | 
				
			||||||
 | 
							sendExistingDocument(document);
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->photoChosen(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](not_null<PhotoData*> photo) {
 | 
				
			||||||
 | 
							sendExistingPhoto(photo);
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->inlineResultChosen(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](
 | 
				
			||||||
 | 
								ChatHelpers::TabbedSelector::InlineChosen chosen) {
 | 
				
			||||||
 | 
							sendInlineResult(chosen.result, chosen.bot);
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::chooseAttach() {
 | 
				
			||||||
 | 
						if (const auto error = Data::RestrictionError(
 | 
				
			||||||
 | 
							_history->peer,
 | 
				
			||||||
 | 
							ChatRestriction::f_send_media)) {
 | 
				
			||||||
 | 
							Ui::Toast::Show(*error);
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto filter = FileDialog::AllFilesFilter()
 | 
				
			||||||
 | 
							+ qsl(";;Image files (*")
 | 
				
			||||||
 | 
							+ cImgExtensions().join(qsl(" *"))
 | 
				
			||||||
 | 
							+ qsl(")");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FileDialog::GetOpenPaths(this, tr::lng_choose_files(tr::now), filter, crl::guard(this, [=](
 | 
				
			||||||
 | 
								FileDialog::OpenResult &&result) {
 | 
				
			||||||
 | 
							if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!result.remoteContent.isEmpty()) {
 | 
				
			||||||
 | 
								auto animated = false;
 | 
				
			||||||
 | 
								auto image = App::readImage(
 | 
				
			||||||
 | 
									result.remoteContent,
 | 
				
			||||||
 | 
									nullptr,
 | 
				
			||||||
 | 
									false,
 | 
				
			||||||
 | 
									&animated);
 | 
				
			||||||
 | 
								if (!image.isNull() && !animated) {
 | 
				
			||||||
 | 
									confirmSendingFiles(
 | 
				
			||||||
 | 
										std::move(image),
 | 
				
			||||||
 | 
										std::move(result.remoteContent),
 | 
				
			||||||
 | 
										CompressConfirm::Auto);
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									uploadFile(result.remoteContent, SendMediaType::File);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								auto list = Storage::PrepareMediaList(
 | 
				
			||||||
 | 
									result.paths,
 | 
				
			||||||
 | 
									st::sendMediaPreviewSize);
 | 
				
			||||||
 | 
								if (list.allFilesForCompress || list.albumIsPossible) {
 | 
				
			||||||
 | 
									confirmSendingFiles(std::move(list), CompressConfirm::Auto);
 | 
				
			||||||
 | 
								} else if (!showSendingFilesError(list)) {
 | 
				
			||||||
 | 
									confirmSendingFiles(std::move(list), CompressConfirm::No);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}), nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ScheduledWidget::confirmSendingFiles(
 | 
				
			||||||
 | 
							Storage::PreparedList &&list,
 | 
				
			||||||
 | 
							CompressConfirm compressed,
 | 
				
			||||||
 | 
							const QString &insertTextOnCancel) {
 | 
				
			||||||
 | 
						if (showSendingFilesError(list)) {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto noCompressOption = (list.files.size() > 1)
 | 
				
			||||||
 | 
							&& !list.allFilesForCompress
 | 
				
			||||||
 | 
							&& !list.albumIsPossible;
 | 
				
			||||||
 | 
						const auto boxCompressConfirm = noCompressOption
 | 
				
			||||||
 | 
							? CompressConfirm::None
 | 
				
			||||||
 | 
							: compressed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//const auto cursor = _field->textCursor();
 | 
				
			||||||
 | 
						//const auto position = cursor.position();
 | 
				
			||||||
 | 
						//const auto anchor = cursor.anchor();
 | 
				
			||||||
 | 
						const auto text = _composeControls->getTextWithAppliedMarkdown();//_field->getTextWithTags();
 | 
				
			||||||
 | 
						using SendLimit = SendFilesBox::SendLimit;
 | 
				
			||||||
 | 
						auto box = Box<SendFilesBox>(
 | 
				
			||||||
 | 
							controller(),
 | 
				
			||||||
 | 
							std::move(list),
 | 
				
			||||||
 | 
							text,
 | 
				
			||||||
 | 
							boxCompressConfirm,
 | 
				
			||||||
 | 
							_history->peer->slowmodeApplied() ? SendLimit::One : SendLimit::Many);
 | 
				
			||||||
 | 
						//_field->setTextWithTags({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						box->setConfirmedCallback(crl::guard(this, [=](
 | 
				
			||||||
 | 
								Storage::PreparedList &&list,
 | 
				
			||||||
 | 
								SendFilesWay way,
 | 
				
			||||||
 | 
								TextWithTags &&caption,
 | 
				
			||||||
 | 
								Api::SendOptions options,
 | 
				
			||||||
 | 
								bool ctrlShiftEnter) {
 | 
				
			||||||
 | 
							if (showSendingFilesError(list)) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const auto type = (way == SendFilesWay::Files)
 | 
				
			||||||
 | 
								? SendMediaType::File
 | 
				
			||||||
 | 
								: SendMediaType::Photo;
 | 
				
			||||||
 | 
							const auto album = (way == SendFilesWay::Album)
 | 
				
			||||||
 | 
								? std::make_shared<SendingAlbum>()
 | 
				
			||||||
 | 
								: nullptr;
 | 
				
			||||||
 | 
							uploadFilesAfterConfirmation(
 | 
				
			||||||
 | 
								std::move(list),
 | 
				
			||||||
 | 
								type,
 | 
				
			||||||
 | 
								std::move(caption),
 | 
				
			||||||
 | 
								MsgId(0),//replyToId(),
 | 
				
			||||||
 | 
								options,
 | 
				
			||||||
 | 
								album);
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
						//box->setCancelledCallback(crl::guard(this, [=] {
 | 
				
			||||||
 | 
						//	_field->setTextWithTags(text);
 | 
				
			||||||
 | 
						//	auto cursor = _field->textCursor();
 | 
				
			||||||
 | 
						//	cursor.setPosition(anchor);
 | 
				
			||||||
 | 
						//	if (position != anchor) {
 | 
				
			||||||
 | 
						//		cursor.setPosition(position, QTextCursor::KeepAnchor);
 | 
				
			||||||
 | 
						//	}
 | 
				
			||||||
 | 
						//	_field->setTextCursor(cursor);
 | 
				
			||||||
 | 
						//	if (!insertTextOnCancel.isEmpty()) {
 | 
				
			||||||
 | 
						//		_field->textCursor().insertText(insertTextOnCancel);
 | 
				
			||||||
 | 
						//	}
 | 
				
			||||||
 | 
						//}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//ActivateWindow(controller());
 | 
				
			||||||
 | 
						const auto shown = Ui::show(std::move(box));
 | 
				
			||||||
 | 
						shown->setCloseByOutsideClick(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ScheduledWidget::confirmSendingFiles(
 | 
				
			||||||
 | 
							QImage &&image,
 | 
				
			||||||
 | 
							QByteArray &&content,
 | 
				
			||||||
 | 
							CompressConfirm compressed,
 | 
				
			||||||
 | 
							const QString &insertTextOnCancel) {
 | 
				
			||||||
 | 
						if (image.isNull()) {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto list = Storage::PrepareMediaFromImage(
 | 
				
			||||||
 | 
							std::move(image),
 | 
				
			||||||
 | 
							std::move(content),
 | 
				
			||||||
 | 
							st::sendMediaPreviewSize);
 | 
				
			||||||
 | 
						return confirmSendingFiles(
 | 
				
			||||||
 | 
							std::move(list),
 | 
				
			||||||
 | 
							compressed,
 | 
				
			||||||
 | 
							insertTextOnCancel);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::uploadFilesAfterConfirmation(
 | 
				
			||||||
 | 
							Storage::PreparedList &&list,
 | 
				
			||||||
 | 
							SendMediaType type,
 | 
				
			||||||
 | 
							TextWithTags &&caption,
 | 
				
			||||||
 | 
							MsgId replyTo,
 | 
				
			||||||
 | 
							Api::SendOptions options,
 | 
				
			||||||
 | 
							std::shared_ptr<SendingAlbum> album) {
 | 
				
			||||||
 | 
						const auto isAlbum = (album != nullptr);
 | 
				
			||||||
 | 
						const auto compressImages = (type == SendMediaType::Photo);
 | 
				
			||||||
 | 
						if (_history->peer->slowmodeApplied()
 | 
				
			||||||
 | 
							&& ((list.files.size() > 1 && !album)
 | 
				
			||||||
 | 
								|| (!list.files.empty()
 | 
				
			||||||
 | 
									&& !caption.text.isEmpty()
 | 
				
			||||||
 | 
									&& !list.canAddCaption(isAlbum, compressImages)))) {
 | 
				
			||||||
 | 
							ShowErrorToast(tr::lng_slowmode_no_many(tr::now));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						auto callback = crl::guard(this, [
 | 
				
			||||||
 | 
							=,
 | 
				
			||||||
 | 
							list = std::move(list),
 | 
				
			||||||
 | 
							caption = std::move(caption),
 | 
				
			||||||
 | 
							// Strange thing, otherwise std::is_copy_constructible is true. O_o
 | 
				
			||||||
 | 
							msvc_bug_workaround = std::make_unique<int>()
 | 
				
			||||||
 | 
						](Api::SendOptions options) mutable {
 | 
				
			||||||
 | 
							auto action = Api::SendAction(_history);
 | 
				
			||||||
 | 
							action.replyTo = replyTo;
 | 
				
			||||||
 | 
							action.options = options;
 | 
				
			||||||
 | 
							session().api().sendFiles(
 | 
				
			||||||
 | 
								std::move(list),
 | 
				
			||||||
 | 
								type,
 | 
				
			||||||
 | 
								std::move(caption),
 | 
				
			||||||
 | 
								album,
 | 
				
			||||||
 | 
								action);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						Ui::show(
 | 
				
			||||||
 | 
							Box(ScheduleBox, std::move(callback), DefaultScheduleTime()),
 | 
				
			||||||
 | 
							LayerOption::KeepOther);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::uploadFile(
 | 
				
			||||||
 | 
							const QByteArray &fileContent,
 | 
				
			||||||
 | 
							SendMediaType type) {
 | 
				
			||||||
 | 
						const auto callback = crl::guard(this, [=](Api::SendOptions options) {
 | 
				
			||||||
 | 
							auto action = Api::SendAction(_history);
 | 
				
			||||||
 | 
							//action.replyTo = replyToId();
 | 
				
			||||||
 | 
							action.options = options;
 | 
				
			||||||
 | 
							session().api().sendFile(fileContent, type, action);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						Ui::show(
 | 
				
			||||||
 | 
							Box(ScheduleBox, callback, DefaultScheduleTime()),
 | 
				
			||||||
 | 
							LayerOption::KeepOther);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ScheduledWidget::showSendingFilesError(
 | 
				
			||||||
 | 
							const Storage::PreparedList &list) const {
 | 
				
			||||||
 | 
						const auto text = [&] {
 | 
				
			||||||
 | 
							const auto error = Data::RestrictionError(
 | 
				
			||||||
 | 
								_history->peer,
 | 
				
			||||||
 | 
								ChatRestriction::f_send_media);
 | 
				
			||||||
 | 
							if (error) {
 | 
				
			||||||
 | 
								return *error;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							using Error = Storage::PreparedList::Error;
 | 
				
			||||||
 | 
							switch (list.error) {
 | 
				
			||||||
 | 
							case Error::None: return QString();
 | 
				
			||||||
 | 
							case Error::EmptyFile:
 | 
				
			||||||
 | 
							case Error::Directory:
 | 
				
			||||||
 | 
							case Error::NonLocalUrl: return tr::lng_send_image_empty(
 | 
				
			||||||
 | 
								tr::now,
 | 
				
			||||||
 | 
								lt_name,
 | 
				
			||||||
 | 
								list.errorData);
 | 
				
			||||||
 | 
							case Error::TooLargeFile: return tr::lng_send_image_too_large(
 | 
				
			||||||
 | 
								tr::now,
 | 
				
			||||||
 | 
								lt_name,
 | 
				
			||||||
 | 
								list.errorData);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return tr::lng_forward_send_files_cant(tr::now);
 | 
				
			||||||
 | 
						}();
 | 
				
			||||||
 | 
						if (text.isEmpty()) {
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ShowErrorToast(text);
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ScheduledWidget::send() {
 | 
					void ScheduledWidget::send() {
 | 
				
			||||||
| 
						 | 
					@ -160,6 +430,123 @@ void ScheduledWidget::send(Api::SendOptions options) {
 | 
				
			||||||
	_composeControls->focus();
 | 
						_composeControls->focus();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::sendExistingDocument(
 | 
				
			||||||
 | 
							not_null<DocumentData*> document) {
 | 
				
			||||||
 | 
						const auto callback = crl::guard(this, [=](Api::SendOptions options) {
 | 
				
			||||||
 | 
							sendExistingDocument(document, options);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						Ui::show(
 | 
				
			||||||
 | 
							Box(ScheduleBox, callback, DefaultScheduleTime()),
 | 
				
			||||||
 | 
							LayerOption::KeepOther);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ScheduledWidget::sendExistingDocument(
 | 
				
			||||||
 | 
							not_null<DocumentData*> document,
 | 
				
			||||||
 | 
							Api::SendOptions options) {
 | 
				
			||||||
 | 
						const auto error = Data::RestrictionError(
 | 
				
			||||||
 | 
							_history->peer,
 | 
				
			||||||
 | 
							ChatRestriction::f_send_stickers);
 | 
				
			||||||
 | 
						if (error) {
 | 
				
			||||||
 | 
							Ui::show(Box<InformBox>(*error), LayerOption::KeepOther);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto message = Api::MessageToSend(_history);
 | 
				
			||||||
 | 
						//message.action.replyTo = replyToId();
 | 
				
			||||||
 | 
						message.action.options = options;
 | 
				
			||||||
 | 
						Api::SendExistingDocument(std::move(message), document);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//if (_fieldAutocomplete->stickersShown()) {
 | 
				
			||||||
 | 
						//	clearFieldText();
 | 
				
			||||||
 | 
						//	//_saveDraftText = true;
 | 
				
			||||||
 | 
						//	//_saveDraftStart = crl::now();
 | 
				
			||||||
 | 
						//	//onDraftSave();
 | 
				
			||||||
 | 
						//	onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
 | 
				
			||||||
 | 
						//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->hidePanelsAnimated();
 | 
				
			||||||
 | 
						_composeControls->focus();
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::sendExistingPhoto(not_null<PhotoData*> photo) {
 | 
				
			||||||
 | 
						const auto callback = crl::guard(this, [=](Api::SendOptions options) {
 | 
				
			||||||
 | 
							sendExistingPhoto(photo, options);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						Ui::show(
 | 
				
			||||||
 | 
							Box(ScheduleBox, callback, DefaultScheduleTime()),
 | 
				
			||||||
 | 
							LayerOption::KeepOther);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ScheduledWidget::sendExistingPhoto(
 | 
				
			||||||
 | 
							not_null<PhotoData*> photo,
 | 
				
			||||||
 | 
							Api::SendOptions options) {
 | 
				
			||||||
 | 
						const auto error = Data::RestrictionError(
 | 
				
			||||||
 | 
							_history->peer,
 | 
				
			||||||
 | 
							ChatRestriction::f_send_media);
 | 
				
			||||||
 | 
						if (error) {
 | 
				
			||||||
 | 
							Ui::show(Box<InformBox>(*error), LayerOption::KeepOther);
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto message = Api::MessageToSend(_history);
 | 
				
			||||||
 | 
						//message.action.replyTo = replyToId();
 | 
				
			||||||
 | 
						message.action.options = options;
 | 
				
			||||||
 | 
						Api::SendExistingPhoto(std::move(message), photo);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->hidePanelsAnimated();
 | 
				
			||||||
 | 
						_composeControls->focus();
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::sendInlineResult(
 | 
				
			||||||
 | 
							not_null<InlineBots::Result*> result,
 | 
				
			||||||
 | 
							not_null<UserData*> bot) {
 | 
				
			||||||
 | 
						const auto errorText = result->getErrorOnSend(_history);
 | 
				
			||||||
 | 
						if (!errorText.isEmpty()) {
 | 
				
			||||||
 | 
							Ui::show(Box<InformBox>(errorText));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const auto callback = crl::guard(this, [=](Api::SendOptions options) {
 | 
				
			||||||
 | 
							sendInlineResult(result, bot, options);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						Ui::show(
 | 
				
			||||||
 | 
							Box(ScheduleBox, callback, DefaultScheduleTime()),
 | 
				
			||||||
 | 
							LayerOption::KeepOther);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ScheduledWidget::sendInlineResult(
 | 
				
			||||||
 | 
							not_null<InlineBots::Result*> result,
 | 
				
			||||||
 | 
							not_null<UserData*> bot,
 | 
				
			||||||
 | 
							Api::SendOptions options) {
 | 
				
			||||||
 | 
						auto action = Api::SendAction(_history);
 | 
				
			||||||
 | 
						action.clearDraft = true;
 | 
				
			||||||
 | 
						//action.replyTo = replyToId();
 | 
				
			||||||
 | 
						action.options = options;
 | 
				
			||||||
 | 
						action.generateLocal = true;
 | 
				
			||||||
 | 
						session().api().sendInlineResult(bot, result, action);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->clear();
 | 
				
			||||||
 | 
						//_saveDraftText = true;
 | 
				
			||||||
 | 
						//_saveDraftStart = crl::now();
 | 
				
			||||||
 | 
						//onDraftSave();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto &bots = cRefRecentInlineBots();
 | 
				
			||||||
 | 
						const auto index = bots.indexOf(bot);
 | 
				
			||||||
 | 
						if (index) {
 | 
				
			||||||
 | 
							if (index > 0) {
 | 
				
			||||||
 | 
								bots.removeAt(index);
 | 
				
			||||||
 | 
							} else if (bots.size() >= RecentInlineBotsLimit) {
 | 
				
			||||||
 | 
								bots.resize(RecentInlineBotsLimit - 1);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							bots.push_front(bot);
 | 
				
			||||||
 | 
							Local::writeRecentHashtagsAndBots();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_composeControls->hidePanelsAnimated();
 | 
				
			||||||
 | 
						_composeControls->focus();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ScheduledWidget::setupScrollDownButton() {
 | 
					void ScheduledWidget::setupScrollDownButton() {
 | 
				
			||||||
	_scrollDown->setClickedCallback([=] {
 | 
						_scrollDown->setClickedCallback([=] {
 | 
				
			||||||
		scrollDownClicked();
 | 
							scrollDownClicked();
 | 
				
			||||||
| 
						 | 
					@ -470,14 +857,14 @@ void ScheduledWidget::highlightSingleNewMessage(
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto firstDifferent = 0;
 | 
						auto firstDifferent = 0;
 | 
				
			||||||
	for (; firstDifferent != _lastSlice.ids.size(); ++firstDifferent) {
 | 
						while (firstDifferent != _lastSlice.ids.size()) {
 | 
				
			||||||
		if (slice.ids[firstDifferent] != _lastSlice.ids[firstDifferent]) {
 | 
							if (slice.ids[firstDifferent] != _lastSlice.ids[firstDifferent]) {
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		++firstDifferent;
 | 
							++firstDifferent;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	auto lastDifferent = slice.ids.size() - 1;
 | 
						auto lastDifferent = slice.ids.size() - 1;
 | 
				
			||||||
	for (; lastDifferent != firstDifferent;) {
 | 
						while (lastDifferent != firstDifferent) {
 | 
				
			||||||
		if (slice.ids[lastDifferent] != _lastSlice.ids[lastDifferent - 1]) {
 | 
							if (slice.ids[lastDifferent] != _lastSlice.ids[lastDifferent - 1]) {
 | 
				
			||||||
			break;
 | 
								break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "data/data_messages.h"
 | 
					#include "data/data_messages.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class History;
 | 
					class History;
 | 
				
			||||||
 | 
					enum class CompressConfirm;
 | 
				
			||||||
 | 
					enum class SendMediaType;
 | 
				
			||||||
 | 
					struct SendingAlbum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Api {
 | 
					namespace Api {
 | 
				
			||||||
struct SendOptions;
 | 
					struct SendOptions;
 | 
				
			||||||
| 
						 | 
					@ -22,6 +25,10 @@ namespace Notify {
 | 
				
			||||||
struct PeerUpdate;
 | 
					struct PeerUpdate;
 | 
				
			||||||
} // namespace Notify
 | 
					} // namespace Notify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Storage {
 | 
				
			||||||
 | 
					struct PreparedList;
 | 
				
			||||||
 | 
					} // namespace Storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace Ui {
 | 
					namespace Ui {
 | 
				
			||||||
class ScrollArea;
 | 
					class ScrollArea;
 | 
				
			||||||
class PlainShadow;
 | 
					class PlainShadow;
 | 
				
			||||||
| 
						 | 
					@ -33,6 +40,10 @@ namespace Profile {
 | 
				
			||||||
class BackButton;
 | 
					class BackButton;
 | 
				
			||||||
} // namespace Profile
 | 
					} // namespace Profile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace InlineBots {
 | 
				
			||||||
 | 
					class Result;
 | 
				
			||||||
 | 
					} // namespace InlineBots
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace HistoryView {
 | 
					namespace HistoryView {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Element;
 | 
					class Element;
 | 
				
			||||||
| 
						 | 
					@ -133,6 +144,42 @@ private:
 | 
				
			||||||
	void send();
 | 
						void send();
 | 
				
			||||||
	void send(Api::SendOptions options);
 | 
						void send(Api::SendOptions options);
 | 
				
			||||||
	void highlightSingleNewMessage(const Data::MessagesSlice &slice);
 | 
						void highlightSingleNewMessage(const Data::MessagesSlice &slice);
 | 
				
			||||||
 | 
						void chooseAttach();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void uploadFile(const QByteArray &fileContent, SendMediaType type);
 | 
				
			||||||
 | 
						bool confirmSendingFiles(
 | 
				
			||||||
 | 
							QImage &&image,
 | 
				
			||||||
 | 
							QByteArray &&content,
 | 
				
			||||||
 | 
							CompressConfirm compressed,
 | 
				
			||||||
 | 
							const QString &insertTextOnCancel = QString());
 | 
				
			||||||
 | 
						bool confirmSendingFiles(
 | 
				
			||||||
 | 
							Storage::PreparedList &&list,
 | 
				
			||||||
 | 
							CompressConfirm compressed,
 | 
				
			||||||
 | 
							const QString &insertTextOnCancel = QString());
 | 
				
			||||||
 | 
						bool showSendingFilesError(const Storage::PreparedList &list) const;
 | 
				
			||||||
 | 
						void uploadFilesAfterConfirmation(
 | 
				
			||||||
 | 
							Storage::PreparedList &&list,
 | 
				
			||||||
 | 
							SendMediaType type,
 | 
				
			||||||
 | 
							TextWithTags &&caption,
 | 
				
			||||||
 | 
							MsgId replyTo,
 | 
				
			||||||
 | 
							Api::SendOptions options,
 | 
				
			||||||
 | 
							std::shared_ptr<SendingAlbum> album);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void sendExistingDocument(not_null<DocumentData*> document);
 | 
				
			||||||
 | 
						bool sendExistingDocument(
 | 
				
			||||||
 | 
							not_null<DocumentData*> document,
 | 
				
			||||||
 | 
							Api::SendOptions options);
 | 
				
			||||||
 | 
						void sendExistingPhoto(not_null<PhotoData*> photo);
 | 
				
			||||||
 | 
						bool sendExistingPhoto(
 | 
				
			||||||
 | 
							not_null<PhotoData*> photo,
 | 
				
			||||||
 | 
							Api::SendOptions options);
 | 
				
			||||||
 | 
						void sendInlineResult(
 | 
				
			||||||
 | 
							not_null<InlineBots::Result*> result,
 | 
				
			||||||
 | 
							not_null<UserData*> bot);
 | 
				
			||||||
 | 
						void sendInlineResult(
 | 
				
			||||||
 | 
							not_null<InlineBots::Result*> result,
 | 
				
			||||||
 | 
							not_null<UserData*> bot,
 | 
				
			||||||
 | 
							Api::SendOptions options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const not_null<History*> _history;
 | 
						const not_null<History*> _history;
 | 
				
			||||||
	object_ptr<Ui::ScrollArea> _scroll;
 | 
						object_ptr<Ui::ScrollArea> _scroll;
 | 
				
			||||||
| 
						 | 
					@ -151,6 +198,7 @@ private:
 | 
				
			||||||
	object_ptr<Ui::HistoryDownButton> _scrollDown;
 | 
						object_ptr<Ui::HistoryDownButton> _scrollDown;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Data::MessagesSlice _lastSlice;
 | 
						Data::MessagesSlice _lastSlice;
 | 
				
			||||||
 | 
						bool _choosingAttach = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue