1135 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1135 lines
		
	
	
	
		
			30 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
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 "boxes/create_poll_box.h"
 | 
						|
 | 
						|
#include "lang/lang_keys.h"
 | 
						|
#include "data/data_poll.h"
 | 
						|
#include "ui/toast/toast.h"
 | 
						|
#include "ui/wrap/vertical_layout.h"
 | 
						|
#include "ui/wrap/slide_wrap.h"
 | 
						|
#include "ui/wrap/fade_wrap.h"
 | 
						|
#include "ui/widgets/input_fields.h"
 | 
						|
#include "ui/widgets/shadow.h"
 | 
						|
#include "ui/widgets/labels.h"
 | 
						|
#include "ui/widgets/buttons.h"
 | 
						|
#include "ui/widgets/checkbox.h"
 | 
						|
#include "ui/toast/toast.h"
 | 
						|
#include "main/main_session.h"
 | 
						|
#include "core/application.h"
 | 
						|
#include "core/core_settings.h"
 | 
						|
#include "chat_helpers/emoji_suggestions_widget.h"
 | 
						|
#include "chat_helpers/message_field.h"
 | 
						|
#include "chat_helpers/send_context_menu.h"
 | 
						|
#include "history/view/history_view_schedule_box.h"
 | 
						|
#include "settings/settings_common.h"
 | 
						|
#include "base/unique_qptr.h"
 | 
						|
#include "base/event_filter.h"
 | 
						|
#include "base/call_delayed.h"
 | 
						|
#include "window/window_session_controller.h"
 | 
						|
#include "styles/style_layers.h"
 | 
						|
#include "styles/style_boxes.h"
 | 
						|
#include "styles/style_settings.h"
 | 
						|
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kQuestionLimit = 255;
 | 
						|
constexpr auto kMaxOptionsCount = PollData::kMaxOptions;
 | 
						|
constexpr auto kOptionLimit = 100;
 | 
						|
constexpr auto kWarnQuestionLimit = 80;
 | 
						|
constexpr auto kWarnOptionLimit = 30;
 | 
						|
constexpr auto kSolutionLimit = 200;
 | 
						|
constexpr auto kWarnSolutionLimit = 60;
 | 
						|
constexpr auto kErrorLimit = 99;
 | 
						|
 | 
						|
class Options {
 | 
						|
public:
 | 
						|
	Options(
 | 
						|
		not_null<QWidget*> outer,
 | 
						|
		not_null<Ui::VerticalLayout*> container,
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		bool chooseCorrectEnabled);
 | 
						|
 | 
						|
	[[nodiscard]] bool hasOptions() const;
 | 
						|
	[[nodiscard]] bool isValid() const;
 | 
						|
	[[nodiscard]] bool hasCorrect() const;
 | 
						|
	[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
 | 
						|
	void focusFirst();
 | 
						|
 | 
						|
	void enableChooseCorrect(bool enabled);
 | 
						|
 | 
						|
	[[nodiscard]] rpl::producer<int> usedCount() const;
 | 
						|
	[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
 | 
						|
	[[nodiscard]] rpl::producer<> backspaceInFront() const;
 | 
						|
	[[nodiscard]] rpl::producer<> tabbed() const;
 | 
						|
 | 
						|
private:
 | 
						|
	class Option {
 | 
						|
	public:
 | 
						|
		Option(
 | 
						|
			not_null<QWidget*> outer,
 | 
						|
			not_null<Ui::VerticalLayout*> container,
 | 
						|
			not_null<Main::Session*> session,
 | 
						|
			int position,
 | 
						|
			std::shared_ptr<Ui::RadiobuttonGroup> group);
 | 
						|
 | 
						|
		Option(const Option &other) = delete;
 | 
						|
		Option &operator=(const Option &other) = delete;
 | 
						|
 | 
						|
		void toggleRemoveAlways(bool toggled);
 | 
						|
		void enableChooseCorrect(
 | 
						|
			std::shared_ptr<Ui::RadiobuttonGroup> group);
 | 
						|
 | 
						|
		void show(anim::type animated);
 | 
						|
		void destroy(FnMut<void()> done);
 | 
						|
 | 
						|
		[[nodiscard]] bool hasShadow() const;
 | 
						|
		void createShadow();
 | 
						|
		void destroyShadow();
 | 
						|
 | 
						|
		[[nodiscard]] bool isEmpty() const;
 | 
						|
		[[nodiscard]] bool isGood() const;
 | 
						|
		[[nodiscard]] bool isTooLong() const;
 | 
						|
		[[nodiscard]] bool isCorrect() const;
 | 
						|
		[[nodiscard]] bool hasFocus() const;
 | 
						|
		void setFocus() const;
 | 
						|
		void clearValue();
 | 
						|
 | 
						|
		void setPlaceholder() const;
 | 
						|
		void removePlaceholder() const;
 | 
						|
 | 
						|
		not_null<Ui::InputField*> field() const;
 | 
						|
 | 
						|
		[[nodiscard]] PollAnswer toPollAnswer(int index) const;
 | 
						|
 | 
						|
		[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
 | 
						|
 | 
						|
	private:
 | 
						|
		void createRemove();
 | 
						|
		void createWarning();
 | 
						|
		void toggleCorrectSpace(bool visible);
 | 
						|
		void updateFieldGeometry();
 | 
						|
 | 
						|
		base::unique_qptr<Ui::SlideWrap<Ui::RpWidget>> _wrap;
 | 
						|
		not_null<Ui::RpWidget*> _content;
 | 
						|
		base::unique_qptr<Ui::FadeWrapScaled<Ui::Radiobutton>> _correct;
 | 
						|
		Ui::Animations::Simple _correctShown;
 | 
						|
		bool _hasCorrect = false;
 | 
						|
		Ui::InputField *_field = nullptr;
 | 
						|
		base::unique_qptr<Ui::PlainShadow> _shadow;
 | 
						|
		base::unique_qptr<Ui::CrossButton> _remove;
 | 
						|
		rpl::variable<bool> *_removeAlways = nullptr;
 | 
						|
 | 
						|
	};
 | 
						|
 | 
						|
	[[nodiscard]] bool full() const;
 | 
						|
	[[nodiscard]] bool correctShadows() const;
 | 
						|
	void fixShadows();
 | 
						|
	void removeEmptyTail();
 | 
						|
	void addEmptyOption();
 | 
						|
	void checkLastOption();
 | 
						|
	void validateState();
 | 
						|
	void fixAfterErase();
 | 
						|
	void destroy(std::unique_ptr<Option> option);
 | 
						|
	void removeDestroyed(not_null<Option*> field);
 | 
						|
	int findField(not_null<Ui::InputField*> field) const;
 | 
						|
	[[nodiscard]] auto createChooseCorrectGroup()
 | 
						|
		-> std::shared_ptr<Ui::RadiobuttonGroup>;
 | 
						|
 | 
						|
	not_null<QWidget*> _outer;
 | 
						|
	not_null<Ui::VerticalLayout*> _container;
 | 
						|
	const not_null<Main::Session*> _session;
 | 
						|
	std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
 | 
						|
	int _position = 0;
 | 
						|
	std::vector<std::unique_ptr<Option>> _list;
 | 
						|
	std::vector<std::unique_ptr<Option>> _destroyed;
 | 
						|
	rpl::variable<int> _usedCount = 0;
 | 
						|
	bool _hasOptions = false;
 | 
						|
	bool _isValid = false;
 | 
						|
	bool _hasCorrect = false;
 | 
						|
	rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
 | 
						|
	rpl::event_stream<> _backspaceInFront;
 | 
						|
	rpl::event_stream<> _tabbed;
 | 
						|
 | 
						|
};
 | 
						|
 | 
						|
void InitField(
 | 
						|
		not_null<QWidget*> container,
 | 
						|
		not_null<Ui::InputField*> field,
 | 
						|
		not_null<Main::Session*> session) {
 | 
						|
	field->setInstantReplaces(Ui::InstantReplaces::Default());
 | 
						|
	field->setInstantReplacesEnabled(
 | 
						|
		Core::App().settings().replaceEmojiValue());
 | 
						|
	auto options = Ui::Emoji::SuggestionsController::Options();
 | 
						|
	options.suggestExactFirstWord = false;
 | 
						|
	Ui::Emoji::SuggestionsController::Init(
 | 
						|
		container,
 | 
						|
		field,
 | 
						|
		session,
 | 
						|
		options);
 | 
						|
}
 | 
						|
 | 
						|
not_null<Ui::FlatLabel*> CreateWarningLabel(
 | 
						|
		not_null<QWidget*> parent,
 | 
						|
		not_null<Ui::InputField*> field,
 | 
						|
		int valueLimit,
 | 
						|
		int warnLimit) {
 | 
						|
	const auto result = Ui::CreateChild<Ui::FlatLabel>(
 | 
						|
		parent.get(),
 | 
						|
		QString(),
 | 
						|
		st::createPollWarning);
 | 
						|
	result->setAttribute(Qt::WA_TransparentForMouseEvents);
 | 
						|
	QObject::connect(field, &Ui::InputField::changed, [=] {
 | 
						|
		Ui::PostponeCall(crl::guard(field, [=] {
 | 
						|
			const auto length = field->getLastText().size();
 | 
						|
			const auto value = valueLimit - length;
 | 
						|
			const auto shown = (value < warnLimit)
 | 
						|
				&& (field->height() > st::createPollOptionField.heightMin);
 | 
						|
			result->setRichText((value >= 0)
 | 
						|
				? QString::number(value)
 | 
						|
				: textcmdLink(1, QString::number(value)));
 | 
						|
			result->setVisible(shown);
 | 
						|
		}));
 | 
						|
	});
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void FocusAtEnd(not_null<Ui::InputField*> field) {
 | 
						|
	field->setFocus();
 | 
						|
	field->setCursorPosition(field->getLastText().size());
 | 
						|
	field->ensureCursorVisible();
 | 
						|
}
 | 
						|
 | 
						|
Options::Option::Option(
 | 
						|
	not_null<QWidget*> outer,
 | 
						|
	not_null<Ui::VerticalLayout*> container,
 | 
						|
	not_null<Main::Session*> session,
 | 
						|
	int position,
 | 
						|
	std::shared_ptr<Ui::RadiobuttonGroup> group)
 | 
						|
: _wrap(container->insert(
 | 
						|
	position,
 | 
						|
	object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
 | 
						|
		container,
 | 
						|
		object_ptr<Ui::RpWidget>(container))))
 | 
						|
, _content(_wrap->entity())
 | 
						|
, _field(
 | 
						|
	Ui::CreateChild<Ui::InputField>(
 | 
						|
		_content.get(),
 | 
						|
		st::createPollOptionField,
 | 
						|
		Ui::InputField::Mode::NoNewlines,
 | 
						|
		tr::lng_polls_create_option_add())) {
 | 
						|
	InitField(outer, _field, session);
 | 
						|
	_field->setMaxLength(kOptionLimit + kErrorLimit);
 | 
						|
	_field->show();
 | 
						|
	_field->customTab(true);
 | 
						|
 | 
						|
	_wrap->hide(anim::type::instant);
 | 
						|
 | 
						|
	_content->widthValue(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		updateFieldGeometry();
 | 
						|
	}, _field->lifetime());
 | 
						|
 | 
						|
	_field->heightValue(
 | 
						|
	) | rpl::start_with_next([=](int height) {
 | 
						|
		_content->resize(_content->width(), height);
 | 
						|
	}, _field->lifetime());
 | 
						|
 | 
						|
	QObject::connect(_field, &Ui::InputField::changed, [=] {
 | 
						|
		Ui::PostponeCall(crl::guard(_field, [=] {
 | 
						|
			if (_hasCorrect) {
 | 
						|
				_correct->toggle(isGood(), anim::type::normal);
 | 
						|
			}
 | 
						|
		}));
 | 
						|
	});
 | 
						|
 | 
						|
	createShadow();
 | 
						|
	createRemove();
 | 
						|
	createWarning();
 | 
						|
	enableChooseCorrect(group);
 | 
						|
	_correctShown.stop();
 | 
						|
	if (_correct) {
 | 
						|
		_correct->finishAnimating();
 | 
						|
	}
 | 
						|
	updateFieldGeometry();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::hasShadow() const {
 | 
						|
	return (_shadow != nullptr);
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::createShadow() {
 | 
						|
	Expects(_content != nullptr);
 | 
						|
 | 
						|
	if (_shadow) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_shadow.reset(Ui::CreateChild<Ui::PlainShadow>(field().get()));
 | 
						|
	_shadow->show();
 | 
						|
	field()->sizeValue(
 | 
						|
	) | rpl::start_with_next([=](QSize size) {
 | 
						|
		const auto left = st::createPollFieldPadding.left();
 | 
						|
		_shadow->setGeometry(
 | 
						|
			left,
 | 
						|
			size.height() - st::lineWidth,
 | 
						|
			size.width() - left,
 | 
						|
			st::lineWidth);
 | 
						|
	}, _shadow->lifetime());
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::destroyShadow() {
 | 
						|
	_shadow = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::createRemove() {
 | 
						|
	using namespace rpl::mappers;
 | 
						|
 | 
						|
	const auto field = this->field();
 | 
						|
	auto &lifetime = field->lifetime();
 | 
						|
 | 
						|
	const auto remove = Ui::CreateChild<Ui::CrossButton>(
 | 
						|
		field.get(),
 | 
						|
		st::createPollOptionRemove);
 | 
						|
	remove->hide(anim::type::instant);
 | 
						|
 | 
						|
	const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
 | 
						|
	_removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
 | 
						|
 | 
						|
	QObject::connect(field, &Ui::InputField::changed, [=] {
 | 
						|
		// Don't capture 'this'! Because Option is a value type.
 | 
						|
		*toggle = !field->getLastText().isEmpty();
 | 
						|
	});
 | 
						|
	rpl::combine(
 | 
						|
		toggle->value(),
 | 
						|
		_removeAlways->value(),
 | 
						|
		_1 || _2
 | 
						|
	) | rpl::start_with_next([=](bool shown) {
 | 
						|
		remove->toggle(shown, anim::type::normal);
 | 
						|
	}, remove->lifetime());
 | 
						|
 | 
						|
	field->widthValue(
 | 
						|
	) | rpl::start_with_next([=](int width) {
 | 
						|
		remove->moveToRight(
 | 
						|
			st::createPollOptionRemovePosition.x(),
 | 
						|
			st::createPollOptionRemovePosition.y(),
 | 
						|
			width);
 | 
						|
	}, remove->lifetime());
 | 
						|
 | 
						|
	_remove.reset(remove);
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::createWarning() {
 | 
						|
	using namespace rpl::mappers;
 | 
						|
 | 
						|
	const auto field = this->field();
 | 
						|
	const auto warning = CreateWarningLabel(
 | 
						|
		field,
 | 
						|
		field,
 | 
						|
		kOptionLimit,
 | 
						|
		kWarnOptionLimit);
 | 
						|
	rpl::combine(
 | 
						|
		field->sizeValue(),
 | 
						|
		warning->sizeValue()
 | 
						|
	) | rpl::start_with_next([=](QSize size, QSize label) {
 | 
						|
		warning->moveToLeft(
 | 
						|
			(size.width()
 | 
						|
				- label.width()
 | 
						|
				- st::createPollWarningPosition.x()),
 | 
						|
			(size.height()
 | 
						|
				- label.height()
 | 
						|
				- st::createPollWarningPosition.y()),
 | 
						|
			size.width());
 | 
						|
	}, warning->lifetime());
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::isEmpty() const {
 | 
						|
	return field()->getLastText().trimmed().isEmpty();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::isGood() const {
 | 
						|
	return !field()->getLastText().trimmed().isEmpty() && !isTooLong();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::isTooLong() const {
 | 
						|
	return (field()->getLastText().size() > kOptionLimit);
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::isCorrect() const {
 | 
						|
	return isGood() && _correct && _correct->entity()->Checkbox::checked();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::Option::hasFocus() const {
 | 
						|
	return field()->hasFocus();
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::setFocus() const {
 | 
						|
	FocusAtEnd(field());
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::clearValue() {
 | 
						|
	field()->setText(QString());
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::setPlaceholder() const {
 | 
						|
	field()->setPlaceholder(tr::lng_polls_create_option_add());
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::toggleRemoveAlways(bool toggled) {
 | 
						|
	*_removeAlways = toggled;
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::enableChooseCorrect(
 | 
						|
		std::shared_ptr<Ui::RadiobuttonGroup> group) {
 | 
						|
	if (!group) {
 | 
						|
		if (_correct) {
 | 
						|
			_hasCorrect = false;
 | 
						|
			_correct->hide(anim::type::normal);
 | 
						|
			toggleCorrectSpace(false);
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	static auto Index = 0;
 | 
						|
	const auto button = Ui::CreateChild<Ui::FadeWrapScaled<Ui::Radiobutton>>(
 | 
						|
		_content.get(),
 | 
						|
		object_ptr<Ui::Radiobutton>(
 | 
						|
			_content.get(),
 | 
						|
			group,
 | 
						|
			++Index,
 | 
						|
			QString(),
 | 
						|
			st::defaultCheckbox));
 | 
						|
	button->entity()->resize(
 | 
						|
		button->entity()->height(),
 | 
						|
		button->entity()->height());
 | 
						|
	button->hide(anim::type::instant);
 | 
						|
	_content->sizeValue(
 | 
						|
	) | rpl::start_with_next([=](QSize size) {
 | 
						|
		const auto left = st::createPollFieldPadding.left();
 | 
						|
		button->moveToLeft(
 | 
						|
			left,
 | 
						|
			(size.height() - button->heightNoMargins()) / 2);
 | 
						|
	}, button->lifetime());
 | 
						|
	_correct.reset(button);
 | 
						|
	_hasCorrect = true;
 | 
						|
	if (isGood()) {
 | 
						|
		_correct->show(anim::type::normal);
 | 
						|
	} else {
 | 
						|
		_correct->hide(anim::type::instant);
 | 
						|
	}
 | 
						|
	toggleCorrectSpace(true);
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::toggleCorrectSpace(bool visible) {
 | 
						|
	_correctShown.start(
 | 
						|
		[=] { updateFieldGeometry(); },
 | 
						|
		visible ? 0. : 1.,
 | 
						|
		visible ? 1. : 0.,
 | 
						|
		st::fadeWrapDuration);
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::updateFieldGeometry() {
 | 
						|
	const auto shown = _correctShown.value(_hasCorrect ? 1. : 0.);
 | 
						|
	const auto skip = st::defaultRadio.diameter
 | 
						|
		+ st::defaultCheckbox.textPosition.x();
 | 
						|
	const auto left = anim::interpolate(0, skip, shown);
 | 
						|
	const auto width = _content->width() - left;
 | 
						|
	_field->resizeToWidth(_content->width() - left);
 | 
						|
	_field->moveToLeft(left, 0);
 | 
						|
}
 | 
						|
 | 
						|
not_null<Ui::InputField*> Options::Option::field() const {
 | 
						|
	return _field;
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::removePlaceholder() const {
 | 
						|
	field()->setPlaceholder(rpl::single(QString()));
 | 
						|
}
 | 
						|
 | 
						|
PollAnswer Options::Option::toPollAnswer(int index) const {
 | 
						|
	Expects(index >= 0 && index < kMaxOptionsCount);
 | 
						|
 | 
						|
	auto result = PollAnswer{
 | 
						|
		field()->getLastText().trimmed(),
 | 
						|
		QByteArray(1, ('0' + index))
 | 
						|
	};
 | 
						|
	result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
 | 
						|
	return _remove->clicks();
 | 
						|
}
 | 
						|
 | 
						|
Options::Options(
 | 
						|
	not_null<QWidget*> outer,
 | 
						|
	not_null<Ui::VerticalLayout*> container,
 | 
						|
	not_null<Main::Session*> session,
 | 
						|
	bool chooseCorrectEnabled)
 | 
						|
: _outer(outer)
 | 
						|
, _container(container)
 | 
						|
, _session(session)
 | 
						|
, _chooseCorrectGroup(chooseCorrectEnabled
 | 
						|
	? createChooseCorrectGroup()
 | 
						|
	: nullptr)
 | 
						|
, _position(_container->count()) {
 | 
						|
	checkLastOption();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::full() const {
 | 
						|
	return (_list.size() == kMaxOptionsCount);
 | 
						|
}
 | 
						|
 | 
						|
bool Options::hasOptions() const {
 | 
						|
	return _hasOptions;
 | 
						|
}
 | 
						|
 | 
						|
bool Options::isValid() const {
 | 
						|
	return _isValid;
 | 
						|
}
 | 
						|
 | 
						|
bool Options::hasCorrect() const {
 | 
						|
	return _hasCorrect;
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<int> Options::usedCount() const {
 | 
						|
	return _usedCount.value();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<not_null<QWidget*>> Options::scrollToWidget() const {
 | 
						|
	return _scrollToWidget.events();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> Options::backspaceInFront() const {
 | 
						|
	return _backspaceInFront.events();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<> Options::tabbed() const {
 | 
						|
	return _tabbed.events();
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::show(anim::type animated) {
 | 
						|
	_wrap->show(animated);
 | 
						|
}
 | 
						|
 | 
						|
void Options::Option::destroy(FnMut<void()> done) {
 | 
						|
	if (anim::Disabled() || _wrap->isHidden()) {
 | 
						|
		Ui::PostponeCall(std::move(done));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_wrap->hide(anim::type::normal);
 | 
						|
	base::call_delayed(
 | 
						|
		st::slideWrapDuration * 2,
 | 
						|
		_content.get(),
 | 
						|
		std::move(done));
 | 
						|
}
 | 
						|
 | 
						|
std::vector<PollAnswer> Options::toPollAnswers() const {
 | 
						|
	auto result = std::vector<PollAnswer>();
 | 
						|
	result.reserve(_list.size());
 | 
						|
	auto counter = int(0);
 | 
						|
	const auto makeAnswer = [&](const std::unique_ptr<Option> &option) {
 | 
						|
		return option->toPollAnswer(counter++);
 | 
						|
	};
 | 
						|
	ranges::copy(
 | 
						|
		_list
 | 
						|
		| ranges::view::filter(&Option::isGood)
 | 
						|
		| ranges::view::transform(makeAnswer),
 | 
						|
		ranges::back_inserter(result));
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Options::focusFirst() {
 | 
						|
	Expects(!_list.empty());
 | 
						|
 | 
						|
	_list.front()->setFocus();
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
 | 
						|
	auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
 | 
						|
	result->setChangedCallback([=](int) {
 | 
						|
		validateState();
 | 
						|
	});
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Options::enableChooseCorrect(bool enabled) {
 | 
						|
	_chooseCorrectGroup = enabled
 | 
						|
		? createChooseCorrectGroup()
 | 
						|
		: nullptr;
 | 
						|
	for (auto &option : _list) {
 | 
						|
		option->enableChooseCorrect(_chooseCorrectGroup);
 | 
						|
	}
 | 
						|
	validateState();
 | 
						|
}
 | 
						|
 | 
						|
bool Options::correctShadows() const {
 | 
						|
	// Last one should be without shadow.
 | 
						|
	const auto noShadow = ranges::find(
 | 
						|
		_list,
 | 
						|
		true,
 | 
						|
		ranges::not_fn(&Option::hasShadow));
 | 
						|
	return (noShadow == end(_list) - 1);
 | 
						|
}
 | 
						|
 | 
						|
void Options::fixShadows() {
 | 
						|
	if (correctShadows()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	for (auto &option : _list) {
 | 
						|
		option->createShadow();
 | 
						|
	}
 | 
						|
	_list.back()->destroyShadow();
 | 
						|
}
 | 
						|
 | 
						|
void Options::removeEmptyTail() {
 | 
						|
	// Only one option at the end of options list can be empty.
 | 
						|
	// Remove all other trailing empty options.
 | 
						|
	// Only last empty and previous option have non-empty placeholders.
 | 
						|
	const auto focused = ranges::find_if(
 | 
						|
		_list,
 | 
						|
		&Option::hasFocus);
 | 
						|
	const auto end = _list.end();
 | 
						|
	const auto reversed = ranges::view::reverse(_list);
 | 
						|
	const auto emptyItem = ranges::find_if(
 | 
						|
		reversed,
 | 
						|
		ranges::not_fn(&Option::isEmpty)).base();
 | 
						|
	const auto focusLast = (focused > emptyItem) && (focused < end);
 | 
						|
	if (emptyItem == end) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (focusLast) {
 | 
						|
		(*emptyItem)->setFocus();
 | 
						|
	}
 | 
						|
	for (auto i = emptyItem + 1; i != end; ++i) {
 | 
						|
		destroy(std::move(*i));
 | 
						|
	}
 | 
						|
	_list.erase(emptyItem + 1, end);
 | 
						|
	fixAfterErase();
 | 
						|
}
 | 
						|
 | 
						|
void Options::destroy(std::unique_ptr<Option> option) {
 | 
						|
	const auto value = option.get();
 | 
						|
	option->destroy([=] { removeDestroyed(value); });
 | 
						|
	_destroyed.push_back(std::move(option));
 | 
						|
}
 | 
						|
 | 
						|
void Options::fixAfterErase() {
 | 
						|
	Expects(!_list.empty());
 | 
						|
 | 
						|
	const auto last = _list.end() - 1;
 | 
						|
	(*last)->setPlaceholder();
 | 
						|
	(*last)->toggleRemoveAlways(false);
 | 
						|
	if (last != begin(_list)) {
 | 
						|
		(*(last - 1))->setPlaceholder();
 | 
						|
		(*(last - 1))->toggleRemoveAlways(false);
 | 
						|
	}
 | 
						|
	fixShadows();
 | 
						|
}
 | 
						|
 | 
						|
void Options::addEmptyOption() {
 | 
						|
	if (full()) {
 | 
						|
		return;
 | 
						|
	} else if (!_list.empty() && _list.back()->isEmpty()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (_list.size() > 1) {
 | 
						|
		(*(_list.end() - 2))->removePlaceholder();
 | 
						|
		(*(_list.end() - 2))->toggleRemoveAlways(true);
 | 
						|
	}
 | 
						|
	_list.push_back(std::make_unique<Option>(
 | 
						|
		_outer,
 | 
						|
		_container,
 | 
						|
		_session,
 | 
						|
		_position + _list.size() + _destroyed.size(),
 | 
						|
		_chooseCorrectGroup));
 | 
						|
	const auto field = _list.back()->field();
 | 
						|
	QObject::connect(field, &Ui::InputField::submitted, [=] {
 | 
						|
		const auto index = findField(field);
 | 
						|
		if (_list[index]->isGood() && index + 1 < _list.size()) {
 | 
						|
			_list[index + 1]->setFocus();
 | 
						|
		}
 | 
						|
	});
 | 
						|
	QObject::connect(field, &Ui::InputField::changed, [=] {
 | 
						|
		Ui::PostponeCall(crl::guard(field, [=] {
 | 
						|
			validateState();
 | 
						|
		}));
 | 
						|
	});
 | 
						|
	QObject::connect(field, &Ui::InputField::focused, [=] {
 | 
						|
		_scrollToWidget.fire_copy(field);
 | 
						|
	});
 | 
						|
	QObject::connect(field, &Ui::InputField::tabbed, [=] {
 | 
						|
		const auto index = findField(field);
 | 
						|
		if (index + 1 < _list.size()) {
 | 
						|
			_list[index + 1]->setFocus();
 | 
						|
		} else {
 | 
						|
			_tabbed.fire({});
 | 
						|
		}
 | 
						|
	});
 | 
						|
	base::install_event_filter(field, [=](not_null<QEvent*> event) {
 | 
						|
		if (event->type() != QEvent::KeyPress
 | 
						|
			|| !field->getLastText().isEmpty()) {
 | 
						|
			return base::EventFilterResult::Continue;
 | 
						|
		}
 | 
						|
		const auto key = static_cast<QKeyEvent*>(event.get())->key();
 | 
						|
		if (key != Qt::Key_Backspace) {
 | 
						|
			return base::EventFilterResult::Continue;
 | 
						|
		}
 | 
						|
 | 
						|
		const auto index = findField(field);
 | 
						|
		if (index > 0) {
 | 
						|
			_list[index - 1]->setFocus();
 | 
						|
		} else {
 | 
						|
			_backspaceInFront.fire({});
 | 
						|
		}
 | 
						|
		return base::EventFilterResult::Cancel;
 | 
						|
	});
 | 
						|
 | 
						|
	_list.back()->removeClicks(
 | 
						|
	) | rpl::take(1) | rpl::start_with_next([=] {
 | 
						|
		Ui::PostponeCall(crl::guard(field, [=] {
 | 
						|
			Expects(!_list.empty());
 | 
						|
 | 
						|
			const auto item = begin(_list) + findField(field);
 | 
						|
			if (item == _list.end() - 1) {
 | 
						|
				(*item)->clearValue();
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			if ((*item)->hasFocus()) {
 | 
						|
				(*(item + 1))->setFocus();
 | 
						|
			}
 | 
						|
			destroy(std::move(*item));
 | 
						|
			_list.erase(item);
 | 
						|
			fixAfterErase();
 | 
						|
			validateState();
 | 
						|
		}));
 | 
						|
	}, field->lifetime());
 | 
						|
 | 
						|
	_list.back()->show((_list.size() == 1)
 | 
						|
		? anim::type::instant
 | 
						|
		: anim::type::normal);
 | 
						|
	fixShadows();
 | 
						|
}
 | 
						|
 | 
						|
void Options::removeDestroyed(not_null<Option*> option) {
 | 
						|
	const auto i = ranges::find(
 | 
						|
		_destroyed,
 | 
						|
		option.get(),
 | 
						|
		&std::unique_ptr<Option>::get);
 | 
						|
	Assert(i != end(_destroyed));
 | 
						|
	_destroyed.erase(i);
 | 
						|
}
 | 
						|
 | 
						|
void Options::validateState() {
 | 
						|
	checkLastOption();
 | 
						|
	_hasOptions = (ranges::count_if(_list, &Option::isGood) > 1);
 | 
						|
	_isValid = _hasOptions && ranges::none_of(_list, &Option::isTooLong);
 | 
						|
	_hasCorrect = ranges::any_of(_list, &Option::isCorrect);
 | 
						|
 | 
						|
	const auto lastEmpty = !_list.empty() && _list.back()->isEmpty();
 | 
						|
	_usedCount = _list.size() - (lastEmpty ? 1 : 0);
 | 
						|
}
 | 
						|
 | 
						|
int Options::findField(not_null<Ui::InputField*> field) const {
 | 
						|
	const auto result = ranges::find(
 | 
						|
		_list,
 | 
						|
		field,
 | 
						|
		&Option::field) - begin(_list);
 | 
						|
 | 
						|
	Ensures(result >= 0 && result < _list.size());
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Options::checkLastOption() {
 | 
						|
	removeEmptyTail();
 | 
						|
	addEmptyOption();
 | 
						|
}
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
CreatePollBox::CreatePollBox(
 | 
						|
	QWidget*,
 | 
						|
	not_null<Window::SessionController*> controller,
 | 
						|
	PollData::Flags chosen,
 | 
						|
	PollData::Flags disabled,
 | 
						|
	Api::SendType sendType)
 | 
						|
: _controller(controller)
 | 
						|
, _chosen(chosen)
 | 
						|
, _disabled(disabled)
 | 
						|
, _sendType(sendType) {
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<CreatePollBox::Result> CreatePollBox::submitRequests() const {
 | 
						|
	return _submitRequests.events();
 | 
						|
}
 | 
						|
 | 
						|
void CreatePollBox::setInnerFocus() {
 | 
						|
	_setInnerFocus();
 | 
						|
}
 | 
						|
 | 
						|
void CreatePollBox::submitFailed(const QString &error) {
 | 
						|
	Ui::Toast::Show(error);
 | 
						|
}
 | 
						|
 | 
						|
not_null<Ui::InputField*> CreatePollBox::setupQuestion(
 | 
						|
		not_null<Ui::VerticalLayout*> container) {
 | 
						|
	using namespace Settings;
 | 
						|
 | 
						|
	const auto session = &_controller->session();
 | 
						|
	AddSubsectionTitle(container, tr::lng_polls_create_question());
 | 
						|
	const auto question = container->add(
 | 
						|
		object_ptr<Ui::InputField>(
 | 
						|
			container,
 | 
						|
			st::createPollField,
 | 
						|
			Ui::InputField::Mode::MultiLine,
 | 
						|
			tr::lng_polls_create_question_placeholder()),
 | 
						|
		st::createPollFieldPadding);
 | 
						|
	InitField(getDelegate()->outerContainer(), question, session);
 | 
						|
	question->setMaxLength(kQuestionLimit + kErrorLimit);
 | 
						|
	question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
 | 
						|
	question->customTab(true);
 | 
						|
 | 
						|
	const auto warning = CreateWarningLabel(
 | 
						|
		container,
 | 
						|
		question,
 | 
						|
		kQuestionLimit,
 | 
						|
		kWarnQuestionLimit);
 | 
						|
	rpl::combine(
 | 
						|
		question->geometryValue(),
 | 
						|
		warning->sizeValue()
 | 
						|
	) | rpl::start_with_next([=](QRect geometry, QSize label) {
 | 
						|
		warning->moveToLeft(
 | 
						|
			(container->width()
 | 
						|
				- label.width()
 | 
						|
				- st::createPollWarningPosition.x()),
 | 
						|
			(geometry.y()
 | 
						|
				- st::createPollFieldPadding.top()
 | 
						|
				- st::settingsSubsectionTitlePadding.bottom()
 | 
						|
				- st::settingsSubsectionTitle.style.font->height
 | 
						|
				+ st::settingsSubsectionTitle.style.font->ascent
 | 
						|
				- st::createPollWarning.style.font->ascent),
 | 
						|
			geometry.width());
 | 
						|
	}, warning->lifetime());
 | 
						|
 | 
						|
	return question;
 | 
						|
}
 | 
						|
 | 
						|
not_null<Ui::InputField*> CreatePollBox::setupSolution(
 | 
						|
		not_null<Ui::VerticalLayout*> container,
 | 
						|
		rpl::producer<bool> shown) {
 | 
						|
	using namespace Settings;
 | 
						|
 | 
						|
	const auto outer = container->add(
 | 
						|
		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
 | 
						|
			container,
 | 
						|
			object_ptr<Ui::VerticalLayout>(container))
 | 
						|
	)->setDuration(0)->toggleOn(std::move(shown));
 | 
						|
	const auto inner = outer->entity();
 | 
						|
 | 
						|
	const auto session = &_controller->session();
 | 
						|
	AddSkip(inner);
 | 
						|
	AddSubsectionTitle(inner, tr::lng_polls_solution_title());
 | 
						|
	const auto solution = inner->add(
 | 
						|
		object_ptr<Ui::InputField>(
 | 
						|
			inner,
 | 
						|
			st::createPollSolutionField,
 | 
						|
			Ui::InputField::Mode::MultiLine,
 | 
						|
			tr::lng_polls_solution_placeholder()),
 | 
						|
		st::createPollFieldPadding);
 | 
						|
	InitField(getDelegate()->outerContainer(), solution, session);
 | 
						|
	solution->setMaxLength(kSolutionLimit + kErrorLimit);
 | 
						|
	solution->setInstantReplaces(Ui::InstantReplaces::Default());
 | 
						|
	solution->setInstantReplacesEnabled(
 | 
						|
		Core::App().settings().replaceEmojiValue());
 | 
						|
	solution->setMarkdownReplacesEnabled(rpl::single(true));
 | 
						|
	solution->setEditLinkCallback(
 | 
						|
		DefaultEditLinkCallback(_controller, solution));
 | 
						|
	solution->customTab(true);
 | 
						|
 | 
						|
	const auto warning = CreateWarningLabel(
 | 
						|
		inner,
 | 
						|
		solution,
 | 
						|
		kSolutionLimit,
 | 
						|
		kWarnSolutionLimit);
 | 
						|
	rpl::combine(
 | 
						|
		solution->geometryValue(),
 | 
						|
		warning->sizeValue()
 | 
						|
	) | rpl::start_with_next([=](QRect geometry, QSize label) {
 | 
						|
		warning->moveToLeft(
 | 
						|
			(inner->width()
 | 
						|
				- label.width()
 | 
						|
				- st::createPollWarningPosition.x()),
 | 
						|
			(geometry.y()
 | 
						|
				- st::createPollFieldPadding.top()
 | 
						|
				- st::settingsSubsectionTitlePadding.bottom()
 | 
						|
				- st::settingsSubsectionTitle.style.font->height
 | 
						|
				+ st::settingsSubsectionTitle.style.font->ascent
 | 
						|
				- st::createPollWarning.style.font->ascent),
 | 
						|
			geometry.width());
 | 
						|
	}, warning->lifetime());
 | 
						|
 | 
						|
	inner->add(
 | 
						|
		object_ptr<Ui::FlatLabel>(
 | 
						|
			inner,
 | 
						|
			tr::lng_polls_solution_about(),
 | 
						|
			st::boxDividerLabel),
 | 
						|
		st::createPollFieldTitlePadding);
 | 
						|
 | 
						|
	return solution;
 | 
						|
}
 | 
						|
 | 
						|
object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
 | 
						|
	using namespace Settings;
 | 
						|
 | 
						|
	const auto id = rand_value<uint64>();
 | 
						|
	const auto error = lifetime().make_state<Errors>(Error::Question);
 | 
						|
 | 
						|
	auto result = object_ptr<Ui::VerticalLayout>(this);
 | 
						|
	const auto container = result.data();
 | 
						|
 | 
						|
	const auto question = setupQuestion(container);
 | 
						|
	AddDivider(container);
 | 
						|
	AddSkip(container);
 | 
						|
	container->add(
 | 
						|
		object_ptr<Ui::FlatLabel>(
 | 
						|
			container,
 | 
						|
			tr::lng_polls_create_options(),
 | 
						|
			st::settingsSubsectionTitle),
 | 
						|
		st::createPollFieldTitlePadding);
 | 
						|
	const auto options = lifetime().make_state<Options>(
 | 
						|
		getDelegate()->outerContainer(),
 | 
						|
		container,
 | 
						|
		&_controller->session(),
 | 
						|
		(_chosen & PollData::Flag::Quiz));
 | 
						|
	auto limit = options->usedCount() | rpl::after_next([=](int count) {
 | 
						|
		setCloseByEscape(!count);
 | 
						|
		setCloseByOutsideClick(!count);
 | 
						|
	}) | rpl::map([=](int count) {
 | 
						|
		return (count < kMaxOptionsCount)
 | 
						|
			? tr::lng_polls_create_limit(tr::now, lt_count, kMaxOptionsCount - count)
 | 
						|
			: tr::lng_polls_create_maximum(tr::now);
 | 
						|
	}) | rpl::after_next([=] {
 | 
						|
		container->resizeToWidth(container->widthNoMargins());
 | 
						|
	});
 | 
						|
	container->add(
 | 
						|
		object_ptr<Ui::DividerLabel>(
 | 
						|
			container,
 | 
						|
			object_ptr<Ui::FlatLabel>(
 | 
						|
				container,
 | 
						|
				std::move(limit),
 | 
						|
				st::boxDividerLabel),
 | 
						|
			st::createPollLimitPadding));
 | 
						|
 | 
						|
	connect(question, &Ui::InputField::tabbed, [=] {
 | 
						|
		options->focusFirst();
 | 
						|
	});
 | 
						|
 | 
						|
	AddSkip(container);
 | 
						|
	AddSubsectionTitle(container, tr::lng_polls_create_settings());
 | 
						|
 | 
						|
	const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes))
 | 
						|
		? container->add(
 | 
						|
			object_ptr<Ui::Checkbox>(
 | 
						|
				container,
 | 
						|
				tr::lng_polls_create_anonymous(tr::now),
 | 
						|
				!(_chosen & PollData::Flag::PublicVotes),
 | 
						|
				st::defaultCheckbox),
 | 
						|
			st::createPollCheckboxMargin)
 | 
						|
		: nullptr;
 | 
						|
	const auto hasMultiple = !(_chosen & PollData::Flag::Quiz)
 | 
						|
		|| !(_disabled & PollData::Flag::Quiz);
 | 
						|
	const auto multiple = hasMultiple
 | 
						|
		? container->add(
 | 
						|
			object_ptr<Ui::Checkbox>(
 | 
						|
				container,
 | 
						|
				tr::lng_polls_create_multiple_choice(tr::now),
 | 
						|
				(_chosen & PollData::Flag::MultiChoice),
 | 
						|
				st::defaultCheckbox),
 | 
						|
			st::createPollCheckboxMargin)
 | 
						|
		: nullptr;
 | 
						|
	const auto quiz = container->add(
 | 
						|
		object_ptr<Ui::Checkbox>(
 | 
						|
			container,
 | 
						|
			tr::lng_polls_create_quiz_mode(tr::now),
 | 
						|
			(_chosen & PollData::Flag::Quiz),
 | 
						|
			st::defaultCheckbox),
 | 
						|
		st::createPollCheckboxMargin);
 | 
						|
 | 
						|
	const auto solution = setupSolution(
 | 
						|
		container,
 | 
						|
		rpl::single(quiz->checked()) | rpl::then(quiz->checkedChanges()));
 | 
						|
 | 
						|
	options->tabbed(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		if (quiz->checked()) {
 | 
						|
			solution->setFocus();
 | 
						|
		} else {
 | 
						|
			question->setFocus();
 | 
						|
		}
 | 
						|
	}, question->lifetime());
 | 
						|
 | 
						|
	connect(solution, &Ui::InputField::tabbed, [=] {
 | 
						|
		question->setFocus();
 | 
						|
	});
 | 
						|
 | 
						|
	quiz->setDisabled(_disabled & PollData::Flag::Quiz);
 | 
						|
	if (multiple) {
 | 
						|
		multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
 | 
						|
			|| (_chosen & PollData::Flag::Quiz));
 | 
						|
		multiple->events(
 | 
						|
		) | rpl::filter([=](not_null<QEvent*> e) {
 | 
						|
			return (e->type() == QEvent::MouseButtonPress) && quiz->checked();
 | 
						|
		}) | rpl::start_with_next([=] {
 | 
						|
			Ui::Toast::Show(tr::lng_polls_create_one_answer(tr::now));
 | 
						|
		}, multiple->lifetime());
 | 
						|
	}
 | 
						|
 | 
						|
	using namespace rpl::mappers;
 | 
						|
	quiz->checkedChanges(
 | 
						|
	) | rpl::start_with_next([=](bool checked) {
 | 
						|
		if (multiple) {
 | 
						|
			if (checked && multiple->checked()) {
 | 
						|
				multiple->setChecked(false);
 | 
						|
			}
 | 
						|
			multiple->setDisabled(checked
 | 
						|
				|| (_disabled & PollData::Flag::MultiChoice));
 | 
						|
		}
 | 
						|
		options->enableChooseCorrect(checked);
 | 
						|
	}, quiz->lifetime());
 | 
						|
 | 
						|
	const auto isValidQuestion = [=] {
 | 
						|
		const auto text = question->getLastText().trimmed();
 | 
						|
		return !text.isEmpty() && (text.size() <= kQuestionLimit);
 | 
						|
	};
 | 
						|
 | 
						|
	connect(question, &Ui::InputField::submitted, [=] {
 | 
						|
		if (isValidQuestion()) {
 | 
						|
			options->focusFirst();
 | 
						|
		}
 | 
						|
	});
 | 
						|
 | 
						|
	_setInnerFocus = [=] {
 | 
						|
		question->setFocusFast();
 | 
						|
	};
 | 
						|
 | 
						|
	const auto collectResult = [=] {
 | 
						|
		using Flag = PollData::Flag;
 | 
						|
		auto result = PollData(&_controller->session().data(), id);
 | 
						|
		result.question = question->getLastText().trimmed();
 | 
						|
		result.answers = options->toPollAnswers();
 | 
						|
		const auto solutionWithTags = quiz->checked()
 | 
						|
			? solution->getTextWithAppliedMarkdown()
 | 
						|
			: TextWithTags();
 | 
						|
		result.solution = TextWithEntities{
 | 
						|
			solutionWithTags.text,
 | 
						|
			TextUtilities::ConvertTextTagsToEntities(solutionWithTags.tags)
 | 
						|
		};
 | 
						|
		const auto publicVotes = (anonymous && !anonymous->checked());
 | 
						|
		const auto multiChoice = (multiple && multiple->checked());
 | 
						|
		result.setFlags(Flag(0)
 | 
						|
			| (publicVotes ? Flag::PublicVotes : Flag(0))
 | 
						|
			| (multiChoice ? Flag::MultiChoice : Flag(0))
 | 
						|
			| (quiz->checked() ? Flag::Quiz : Flag(0)));
 | 
						|
		return result;
 | 
						|
	};
 | 
						|
	const auto collectError = [=] {
 | 
						|
		if (isValidQuestion()) {
 | 
						|
			*error &= ~Error::Question;
 | 
						|
		} else {
 | 
						|
			*error |= Error::Question;
 | 
						|
		}
 | 
						|
		if (!options->hasOptions()) {
 | 
						|
			*error |= Error::Options;
 | 
						|
		} else if (!options->isValid()) {
 | 
						|
			*error |= Error::Other;
 | 
						|
		} else {
 | 
						|
			*error &= ~(Error::Options | Error::Other);
 | 
						|
		}
 | 
						|
		if (quiz->checked() && !options->hasCorrect()) {
 | 
						|
			*error |= Error::Correct;
 | 
						|
		} else {
 | 
						|
			*error &= ~Error::Correct;
 | 
						|
		}
 | 
						|
		if (quiz->checked()
 | 
						|
			&& solution->getLastText().trimmed().size() > kSolutionLimit) {
 | 
						|
			*error |= Error::Solution;
 | 
						|
		} else {
 | 
						|
			*error &= ~Error::Solution;
 | 
						|
		}
 | 
						|
	};
 | 
						|
	const auto showError = [](tr::phrase<> text) {
 | 
						|
		Ui::Toast::Show(text(tr::now));
 | 
						|
	};
 | 
						|
	const auto send = [=](Api::SendOptions sendOptions) {
 | 
						|
		collectError();
 | 
						|
		if (*error & Error::Question) {
 | 
						|
			showError(tr::lng_polls_choose_question);
 | 
						|
			question->setFocus();
 | 
						|
		} else if (*error & Error::Options) {
 | 
						|
			showError(tr::lng_polls_choose_answers);
 | 
						|
			options->focusFirst();
 | 
						|
		} else if (*error & Error::Correct) {
 | 
						|
			showError(tr::lng_polls_choose_correct);
 | 
						|
		} else if (*error & Error::Solution) {
 | 
						|
			solution->showError();
 | 
						|
		} else if (!*error) {
 | 
						|
			_submitRequests.fire({ collectResult(), sendOptions });
 | 
						|
		}
 | 
						|
	};
 | 
						|
	const auto sendSilent = [=] {
 | 
						|
		send({ .silent = true });
 | 
						|
	};
 | 
						|
	const auto sendScheduled = [=] {
 | 
						|
		Ui::show(
 | 
						|
			HistoryView::PrepareScheduleBox(
 | 
						|
				this,
 | 
						|
				SendMenu::Type::Scheduled,
 | 
						|
				send),
 | 
						|
			Ui::LayerOption::KeepOther);
 | 
						|
	};
 | 
						|
 | 
						|
	options->scrollToWidget(
 | 
						|
	) | rpl::start_with_next([=](not_null<QWidget*> widget) {
 | 
						|
		scrollToWidget(widget);
 | 
						|
	}, lifetime());
 | 
						|
 | 
						|
	options->backspaceInFront(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		FocusAtEnd(question);
 | 
						|
	}, lifetime());
 | 
						|
 | 
						|
	const auto isNormal = (_sendType == Api::SendType::Normal);
 | 
						|
	const auto isScheduled = (_sendType == Api::SendType::Scheduled);
 | 
						|
 | 
						|
	const auto submit = addButton(
 | 
						|
		isNormal
 | 
						|
			? tr::lng_polls_create_button()
 | 
						|
			: tr::lng_schedule_button(),
 | 
						|
		[=] { isNormal ? send({}) : sendScheduled(); });
 | 
						|
	if (isNormal || isScheduled) {
 | 
						|
		const auto sendMenuType = [=] {
 | 
						|
			collectError();
 | 
						|
			return (*error || isScheduled)
 | 
						|
				? SendMenu::Type::Disabled
 | 
						|
				: SendMenu::Type::Scheduled;
 | 
						|
		};
 | 
						|
		SendMenu::SetupMenuAndShortcuts(
 | 
						|
			submit.data(),
 | 
						|
			sendMenuType,
 | 
						|
			sendSilent,
 | 
						|
			sendScheduled);
 | 
						|
	}
 | 
						|
	addButton(tr::lng_cancel(), [=] { closeBox(); });
 | 
						|
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void CreatePollBox::prepare() {
 | 
						|
	setTitle(tr::lng_polls_create_title());
 | 
						|
 | 
						|
	const auto inner = setInnerWidget(setupContent());
 | 
						|
 | 
						|
	setDimensionsToContent(st::boxWideWidth, inner);
 | 
						|
}
 |