mirror of https://github.com/procxx/kepka.git
				
				
				
			Add create poll box from groups three-dot menu.
This commit is contained in:
		
							parent
							
								
									74c1db740d
								
							
						
					
					
						commit
						b6f7832745
					
				| 
						 | 
					@ -1836,6 +1836,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
"lng_polls_stop" = "Stop poll";
 | 
					"lng_polls_stop" = "Stop poll";
 | 
				
			||||||
"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
 | 
					"lng_polls_stop_warning" = "If you stop this poll now, nobody will be able to vote in it anymore. This action cannot be undone.";
 | 
				
			||||||
"lng_polls_stop_sure" = "Stop";
 | 
					"lng_polls_stop_sure" = "Stop";
 | 
				
			||||||
 | 
					"lng_polls_create" = "Create poll";
 | 
				
			||||||
 | 
					"lng_polls_create_title" = "New poll";
 | 
				
			||||||
 | 
					"lng_polls_create_question" = "Question";
 | 
				
			||||||
 | 
					"lng_polls_create_question_placeholder" = "Ask a question";
 | 
				
			||||||
 | 
					"lng_polls_create_options" = "Poll options";
 | 
				
			||||||
 | 
					"lng_polls_create_option_add" = "Add an option...";
 | 
				
			||||||
 | 
					"lng_polls_create_limit#one" = "You can add {count} more option.";
 | 
				
			||||||
 | 
					"lng_polls_create_limit#other" = "You can add {count} more options.";
 | 
				
			||||||
 | 
					"lng_polls_create_maximum" = "You have added the maximum number of options.";
 | 
				
			||||||
 | 
					"lng_polls_create_button" = "Create";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Wnd specific
 | 
					// Wnd specific
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4752,36 +4752,6 @@ void ApiWrap::sendExistingDocument(
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	performRequest();
 | 
						performRequest();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	AssertIsDebug();
 | 
					 | 
				
			||||||
	auto answers = QVector<MTPPollAnswer>{
 | 
					 | 
				
			||||||
		MTP_pollAnswer(MTP_string("first option"), MTP_bytes("a")),
 | 
					 | 
				
			||||||
		MTP_pollAnswer(MTP_string("second option"), MTP_bytes("b")),
 | 
					 | 
				
			||||||
		MTP_pollAnswer(MTP_string("third very very very very very very "
 | 
					 | 
				
			||||||
		"very very very very very very option"), MTP_bytes("c")),
 | 
					 | 
				
			||||||
		MTP_pollAnswer(MTP_string("fourth option"), MTP_bytes("d")),
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
	history->sendRequestId = request(MTPmessages_SendMedia(
 | 
					 | 
				
			||||||
		MTP_flags(sendFlags),
 | 
					 | 
				
			||||||
		peer->input,
 | 
					 | 
				
			||||||
		MTP_int(replyTo),
 | 
					 | 
				
			||||||
		MTP_inputMediaPoll(
 | 
					 | 
				
			||||||
			MTP_poll(
 | 
					 | 
				
			||||||
				MTP_long(rand_value<uint64>()),
 | 
					 | 
				
			||||||
				MTP_flags(0),
 | 
					 | 
				
			||||||
				MTP_string("Very very very very very very very very very very "
 | 
					 | 
				
			||||||
				"very very very long poll question text"),
 | 
					 | 
				
			||||||
				MTP_vector<MTPPollAnswer>(answers))),
 | 
					 | 
				
			||||||
		MTP_string(captionText),
 | 
					 | 
				
			||||||
		MTP_long(rand_value<uint64>()),
 | 
					 | 
				
			||||||
		MTPnullMarkup,
 | 
					 | 
				
			||||||
		sentEntities
 | 
					 | 
				
			||||||
	)).done([=](const MTPUpdates &result) {
 | 
					 | 
				
			||||||
		applyUpdates(result);
 | 
					 | 
				
			||||||
	}).fail(
 | 
					 | 
				
			||||||
		base::duplicate(*failHandler)
 | 
					 | 
				
			||||||
	).afterRequest(history->sendRequestId
 | 
					 | 
				
			||||||
	).send();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (const auto main = App::main()) {
 | 
						if (const auto main = App::main()) {
 | 
				
			||||||
		main->finishForwarding(history);
 | 
							main->finishForwarding(history);
 | 
				
			||||||
		if (document->sticker()) {
 | 
							if (document->sticker()) {
 | 
				
			||||||
| 
						 | 
					@ -5344,6 +5314,45 @@ void ApiWrap::setSelfDestructDays(int days) {
 | 
				
			||||||
	_selfDestructChanges.fire_copy(days);
 | 
						_selfDestructChanges.fire_copy(days);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void ApiWrap::createPoll(
 | 
				
			||||||
 | 
							const PollData &data,
 | 
				
			||||||
 | 
							const SendOptions &options,
 | 
				
			||||||
 | 
							FnMut<void()> done,
 | 
				
			||||||
 | 
							FnMut<void(const RPCError &error)> fail) {
 | 
				
			||||||
 | 
						sendAction(options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto history = options.history;
 | 
				
			||||||
 | 
						const auto peer = history->peer;
 | 
				
			||||||
 | 
						auto sendFlags = MTPmessages_SendMedia::Flags(0);
 | 
				
			||||||
 | 
						if (options.replyTo) {
 | 
				
			||||||
 | 
							sendFlags |= MTPmessages_SendMedia::Flag::f_reply_to_msg_id;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const auto channelPost = peer->isChannel() && !peer->isMegagroup();
 | 
				
			||||||
 | 
						const auto silentPost = channelPost
 | 
				
			||||||
 | 
							&& _session->data().notifySilentPosts(peer);
 | 
				
			||||||
 | 
						if (silentPost) {
 | 
				
			||||||
 | 
							sendFlags |= MTPmessages_SendMedia::Flag::f_silent;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto replyTo = options.replyTo;
 | 
				
			||||||
 | 
						history->sendRequestId = request(MTPmessages_SendMedia(
 | 
				
			||||||
 | 
							MTP_flags(sendFlags),
 | 
				
			||||||
 | 
							peer->input,
 | 
				
			||||||
 | 
							MTP_int(replyTo),
 | 
				
			||||||
 | 
							MTP_inputMediaPoll(PollDataToMTP(&data)),
 | 
				
			||||||
 | 
							MTP_string(QString()),
 | 
				
			||||||
 | 
							MTP_long(rand_value<uint64>()),
 | 
				
			||||||
 | 
							MTPReplyMarkup(),
 | 
				
			||||||
 | 
							MTPVector<MTPMessageEntity>()
 | 
				
			||||||
 | 
						)).done([=, done = std::move(done)](const MTPUpdates &result) mutable {
 | 
				
			||||||
 | 
							applyUpdates(result);
 | 
				
			||||||
 | 
							done();
 | 
				
			||||||
 | 
						}).fail([=, fail = std::move(fail)](const RPCError &error) mutable {
 | 
				
			||||||
 | 
							fail(error);
 | 
				
			||||||
 | 
						}).afterRequest(history->sendRequestId
 | 
				
			||||||
 | 
						).send();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void ApiWrap::sendPollVotes(
 | 
					void ApiWrap::sendPollVotes(
 | 
				
			||||||
		FullMsgId itemId,
 | 
							FullMsgId itemId,
 | 
				
			||||||
		const std::vector<QByteArray> &options) {
 | 
							const std::vector<QByteArray> &options) {
 | 
				
			||||||
| 
						 | 
					@ -5385,29 +5394,12 @@ void ApiWrap::closePoll(FullMsgId itemId) {
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const auto convert = [](const PollAnswer &answer) {
 | 
					 | 
				
			||||||
		return MTP_pollAnswer(
 | 
					 | 
				
			||||||
			MTP_string(answer.text),
 | 
					 | 
				
			||||||
			MTP_bytes(answer.option));
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	auto answers = QVector<MTPPollAnswer>();
 | 
					 | 
				
			||||||
	answers.reserve(poll->answers.size());
 | 
					 | 
				
			||||||
	ranges::transform(
 | 
					 | 
				
			||||||
		poll->answers,
 | 
					 | 
				
			||||||
		ranges::back_inserter(answers),
 | 
					 | 
				
			||||||
		convert);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const auto requestId = request(MTPmessages_EditMessage(
 | 
						const auto requestId = request(MTPmessages_EditMessage(
 | 
				
			||||||
		MTP_flags(MTPmessages_EditMessage::Flag::f_media),
 | 
							MTP_flags(MTPmessages_EditMessage::Flag::f_media),
 | 
				
			||||||
		item->history()->peer->input,
 | 
							item->history()->peer->input,
 | 
				
			||||||
		MTP_int(item->id),
 | 
							MTP_int(item->id),
 | 
				
			||||||
		MTPstring(),
 | 
							MTPstring(),
 | 
				
			||||||
		MTP_inputMediaPoll(MTP_poll(
 | 
							MTP_inputMediaPoll(PollDataToMTP(poll)),
 | 
				
			||||||
			MTP_long(poll->id),
 | 
					 | 
				
			||||||
			MTP_flags(MTPDpoll::Flag::f_closed),
 | 
					 | 
				
			||||||
			MTP_string(poll->question),
 | 
					 | 
				
			||||||
			MTP_vector<MTPPollAnswer>(answers))),
 | 
					 | 
				
			||||||
		MTPReplyMarkup(),
 | 
							MTPReplyMarkup(),
 | 
				
			||||||
		MTPVector<MTPMessageEntity>()
 | 
							MTPVector<MTPMessageEntity>()
 | 
				
			||||||
	)).done([=](const MTPUpdates &result) {
 | 
						)).done([=](const MTPUpdates &result) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -377,6 +377,11 @@ public:
 | 
				
			||||||
	rpl::producer<int> selfDestructValue() const;
 | 
						rpl::producer<int> selfDestructValue() const;
 | 
				
			||||||
	void saveSelfDestruct(int days);
 | 
						void saveSelfDestruct(int days);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void createPoll(
 | 
				
			||||||
 | 
							const PollData &data,
 | 
				
			||||||
 | 
							const SendOptions &options,
 | 
				
			||||||
 | 
							FnMut<void()> done,
 | 
				
			||||||
 | 
							FnMut<void(const RPCError &error)> fail);
 | 
				
			||||||
	void sendPollVotes(
 | 
						void sendPollVotes(
 | 
				
			||||||
		FullMsgId itemId,
 | 
							FullMsgId itemId,
 | 
				
			||||||
		const std::vector<QByteArray> &options);
 | 
							const std::vector<QByteArray> &options);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -408,7 +408,9 @@ bool Application::notify(QObject *receiver, QEvent *e) {
 | 
				
			||||||
		_loopNestingLevel = _previousLoopNestingLevels.back();
 | 
							_loopNestingLevel = _previousLoopNestingLevels.back();
 | 
				
			||||||
		_previousLoopNestingLevels.pop_back();
 | 
							_previousLoopNestingLevels.pop_back();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	processPostponedCalls(--_eventNestingLevel);
 | 
						const auto processTillLevel = _eventNestingLevel - 1;
 | 
				
			||||||
 | 
						processPostponedCalls(processTillLevel);
 | 
				
			||||||
 | 
						_eventNestingLevel = processTillLevel;
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -834,3 +834,53 @@ themesScroll: ScrollArea(defaultScrollArea) {
 | 
				
			||||||
	bottomsh: 0px;
 | 
						bottomsh: 0px;
 | 
				
			||||||
	topsh: 0px;
 | 
						topsh: 0px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					createPollField: InputField(defaultInputField) {
 | 
				
			||||||
 | 
						font: boxTextFont;
 | 
				
			||||||
 | 
						textMargins: margins(0px, 0px, 0px, 0px);
 | 
				
			||||||
 | 
						textAlign: align(left);
 | 
				
			||||||
 | 
						heightMin: 36px;
 | 
				
			||||||
 | 
						heightMax: 86px;
 | 
				
			||||||
 | 
						placeholderFg: placeholderFg;
 | 
				
			||||||
 | 
						placeholderFgActive: placeholderFgActive;
 | 
				
			||||||
 | 
						placeholderFgError: placeholderFgActive;
 | 
				
			||||||
 | 
						placeholderMargins: margins(2px, 0px, 2px, 0px);
 | 
				
			||||||
 | 
						placeholderAlign: align(topleft);
 | 
				
			||||||
 | 
						placeholderScale: 0.;
 | 
				
			||||||
 | 
						placeholderFont: boxTextFont;
 | 
				
			||||||
 | 
						placeholderShift: -50px;
 | 
				
			||||||
 | 
						border: 0px;
 | 
				
			||||||
 | 
						borderActive: 0px;
 | 
				
			||||||
 | 
						duration: 100;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					createPollFieldPadding: margins(22px, 5px, 22px, 5px);
 | 
				
			||||||
 | 
					createPollOptionField: InputField(createPollField) {
 | 
				
			||||||
 | 
						textMargins: margins(22px, 8px, 40px, 8px);
 | 
				
			||||||
 | 
						placeholderMargins: margins(2px, 0px, 2px, 0px);
 | 
				
			||||||
 | 
						heightMax: 64px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					createPollLimitLabel: FlatLabel(defaultFlatLabel) {
 | 
				
			||||||
 | 
						minWidth: 274px;
 | 
				
			||||||
 | 
						align: align(topleft);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					createPollOptionRemove: CrossButton {
 | 
				
			||||||
 | 
						width: 22px;
 | 
				
			||||||
 | 
						height: 22px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cross: CrossAnimation {
 | 
				
			||||||
 | 
							size: 22px;
 | 
				
			||||||
 | 
							skip: 6px;
 | 
				
			||||||
 | 
							stroke: 1px;
 | 
				
			||||||
 | 
							minScale: 0.3;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						crossFg: boxTitleCloseFg;
 | 
				
			||||||
 | 
						crossFgOver: boxTitleCloseFgOver;
 | 
				
			||||||
 | 
						crossPosition: point(0px, 0px);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						duration: 150;
 | 
				
			||||||
 | 
						loadingPeriod: 1000;
 | 
				
			||||||
 | 
						ripple: RippleAnimation(defaultRippleAnimation) {
 | 
				
			||||||
 | 
							color: windowBgOver;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					createPollOptionRemovePosition: point(10px, 7px);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,546 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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/widgets/input_fields.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/shadow.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/labels.h"
 | 
				
			||||||
 | 
					#include "ui/widgets/buttons.h"
 | 
				
			||||||
 | 
					#include "chat_helpers/emoji_suggestions_widget.h"
 | 
				
			||||||
 | 
					#include "settings/settings_common.h"
 | 
				
			||||||
 | 
					#include "base/unique_qptr.h"
 | 
				
			||||||
 | 
					#include "styles/style_boxes.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					constexpr auto kQuestionLimit = 255;
 | 
				
			||||||
 | 
					constexpr auto kMaxOptionsCount = 10;
 | 
				
			||||||
 | 
					constexpr auto kOptionLimit = 100;
 | 
				
			||||||
 | 
					constexpr auto kWarnQuestionLimit = 80;
 | 
				
			||||||
 | 
					constexpr auto kWarnOptionLimit = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Options {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						Options(
 | 
				
			||||||
 | 
							not_null<QWidget*> outer,
 | 
				
			||||||
 | 
							not_null<Ui::VerticalLayout*> container);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isValid() const;
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<bool> isValidChanged() const;
 | 
				
			||||||
 | 
						[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
 | 
				
			||||||
 | 
						void focusFirst();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<int> usedCount() const;
 | 
				
			||||||
 | 
						[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						class Option {
 | 
				
			||||||
 | 
						public:
 | 
				
			||||||
 | 
							static Option Create(
 | 
				
			||||||
 | 
								not_null<QWidget*> outer,
 | 
				
			||||||
 | 
								not_null<Ui::VerticalLayout*> container,
 | 
				
			||||||
 | 
								int position);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[[nodisacrd]] bool hasShadow() const;
 | 
				
			||||||
 | 
							void createShadow();
 | 
				
			||||||
 | 
							//void destroyShadow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							void createRemove();
 | 
				
			||||||
 | 
							void toggleRemoveAlways(bool toggled);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[[nodiscard]] bool isEmpty() const;
 | 
				
			||||||
 | 
							[[nodiscard]] bool isGood() 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(char id) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private:
 | 
				
			||||||
 | 
							Option() = default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							base::unique_qptr<Ui::InputField> _field;
 | 
				
			||||||
 | 
							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();
 | 
				
			||||||
 | 
						int findField(not_null<Ui::InputField*> field) const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						not_null<QWidget*> _outer;
 | 
				
			||||||
 | 
						not_null<Ui::VerticalLayout*> _container;
 | 
				
			||||||
 | 
						int _position = 0;
 | 
				
			||||||
 | 
						std::vector<Option> _list;
 | 
				
			||||||
 | 
						rpl::variable<bool> _valid = false;
 | 
				
			||||||
 | 
						rpl::variable<int> _usedCount = 0;
 | 
				
			||||||
 | 
						rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void InitField(
 | 
				
			||||||
 | 
							not_null<QWidget*> container,
 | 
				
			||||||
 | 
							not_null<Ui::InputField*> field) {
 | 
				
			||||||
 | 
						field->setInstantReplaces(Ui::InstantReplaces::Default());
 | 
				
			||||||
 | 
						field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
 | 
				
			||||||
 | 
						Ui::Emoji::SuggestionsController::Init(container, field);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Options::Option Options::Option::Create(
 | 
				
			||||||
 | 
							not_null<QWidget*> outer,
 | 
				
			||||||
 | 
							not_null<Ui::VerticalLayout*> container,
 | 
				
			||||||
 | 
							int position) {
 | 
				
			||||||
 | 
						auto result = Option();
 | 
				
			||||||
 | 
						const auto field = container->insert(
 | 
				
			||||||
 | 
							position,
 | 
				
			||||||
 | 
							object_ptr<Ui::InputField>(
 | 
				
			||||||
 | 
								container,
 | 
				
			||||||
 | 
								st::createPollOptionField,
 | 
				
			||||||
 | 
								Ui::InputField::Mode::MultiLine,
 | 
				
			||||||
 | 
								langFactory(lng_polls_create_option_add)));
 | 
				
			||||||
 | 
						InitField(outer, field);
 | 
				
			||||||
 | 
						result._field.reset(field);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result.createShadow();
 | 
				
			||||||
 | 
						result.createRemove();
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::Option::hasShadow() const {
 | 
				
			||||||
 | 
						return (_shadow != nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::createShadow() {
 | 
				
			||||||
 | 
						Expects(_field != nullptr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (_shadow) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const auto value = Ui::CreateChild<Ui::PlainShadow>(_field.get());
 | 
				
			||||||
 | 
						value->show();
 | 
				
			||||||
 | 
						_field->sizeValue(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](QSize size) {
 | 
				
			||||||
 | 
							const auto left = st::createPollFieldPadding.left();
 | 
				
			||||||
 | 
							value->setGeometry(
 | 
				
			||||||
 | 
								left,
 | 
				
			||||||
 | 
								size.height() - st::lineWidth,
 | 
				
			||||||
 | 
								size.width() - left,
 | 
				
			||||||
 | 
								st::lineWidth);
 | 
				
			||||||
 | 
						}, value->lifetime());
 | 
				
			||||||
 | 
						_shadow.reset(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//void Options::Option::destroyShadow() {
 | 
				
			||||||
 | 
					//	_shadow = nullptr;
 | 
				
			||||||
 | 
					//}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::createRemove() {
 | 
				
			||||||
 | 
						using namespace rpl::mappers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto field = _field.get();
 | 
				
			||||||
 | 
						auto &lifetime = field->lifetime();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto remove = Ui::CreateChild<Ui::CrossButton>(
 | 
				
			||||||
 | 
							field,
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::Option::isEmpty() const {
 | 
				
			||||||
 | 
						return _field->getLastText().trimmed().isEmpty();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::Option::isGood() const {
 | 
				
			||||||
 | 
						const auto text = _field->getLastText().trimmed();
 | 
				
			||||||
 | 
						return !text.isEmpty() && (text.size() <= kOptionLimit);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::Option::hasFocus() const {
 | 
				
			||||||
 | 
						return _field->hasFocus();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::setFocus() const {
 | 
				
			||||||
 | 
						_field->setFocus();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::clearValue() {
 | 
				
			||||||
 | 
						_field->setText(QString());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::setPlaceholder() const {
 | 
				
			||||||
 | 
						_field->setPlaceholder(langFactory(lng_polls_create_option_add));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::toggleRemoveAlways(bool toggled) {
 | 
				
			||||||
 | 
						*_removeAlways = toggled;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					not_null<Ui::InputField*> Options::Option::field() const {
 | 
				
			||||||
 | 
						return _field.get();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::Option::removePlaceholder() const {
 | 
				
			||||||
 | 
						_field->setPlaceholder(nullptr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PollAnswer Options::Option::toPollAnswer(char id) const {
 | 
				
			||||||
 | 
						return PollAnswer{
 | 
				
			||||||
 | 
							_field->getLastText().trimmed(),
 | 
				
			||||||
 | 
							QByteArray(1, id)
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
 | 
				
			||||||
 | 
						return _remove->clicks();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Options::Options(
 | 
				
			||||||
 | 
						not_null<QWidget*> outer,
 | 
				
			||||||
 | 
						not_null<Ui::VerticalLayout*> container)
 | 
				
			||||||
 | 
					: _outer(outer)
 | 
				
			||||||
 | 
					, _container(container)
 | 
				
			||||||
 | 
					, _position(_container->count()) {
 | 
				
			||||||
 | 
						checkLastOption();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::full() const {
 | 
				
			||||||
 | 
						return (_list.size() == kMaxOptionsCount);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Options::isValid() const {
 | 
				
			||||||
 | 
						return _valid.current();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<bool> Options::isValidChanged() const {
 | 
				
			||||||
 | 
						return _valid.changes();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<int> Options::usedCount() const {
 | 
				
			||||||
 | 
						return _usedCount.value();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<not_null<QWidget*>> Options::scrollToWidget() const {
 | 
				
			||||||
 | 
						return _scrollToWidget.events();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::vector<PollAnswer> Options::toPollAnswers() const {
 | 
				
			||||||
 | 
						auto result = std::vector<PollAnswer>();
 | 
				
			||||||
 | 
						result.reserve(_list.size());
 | 
				
			||||||
 | 
						auto counter = char(0);
 | 
				
			||||||
 | 
						const auto makeAnswer = [&](const 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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//bool Options::correctShadows() const {
 | 
				
			||||||
 | 
					//	// Last one should be without shadow if all options were used.
 | 
				
			||||||
 | 
					//	const auto noShadow = ranges::find(
 | 
				
			||||||
 | 
					//		_list,
 | 
				
			||||||
 | 
					//		true,
 | 
				
			||||||
 | 
					//		ranges::not_fn(&Option::hasShadow));
 | 
				
			||||||
 | 
					//	return (noShadow == end(_list) - (full() ? 1 : 0));
 | 
				
			||||||
 | 
					//}
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//void Options::fixShadows() {
 | 
				
			||||||
 | 
					//	if (correctShadows()) {
 | 
				
			||||||
 | 
					//		return;
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	for (auto &option : _list) {
 | 
				
			||||||
 | 
					//		option.createShadow();
 | 
				
			||||||
 | 
					//	}
 | 
				
			||||||
 | 
					//	if (full()) {
 | 
				
			||||||
 | 
					//		_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();
 | 
				
			||||||
 | 
						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();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_list.erase(emptyItem + 1, end);
 | 
				
			||||||
 | 
						fixAfterErase();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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(Option::Create(
 | 
				
			||||||
 | 
							_outer,
 | 
				
			||||||
 | 
							_container,
 | 
				
			||||||
 | 
							_position + _list.size()));
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_list.back().removeClicks(
 | 
				
			||||||
 | 
						) | 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();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								_list.erase(item);
 | 
				
			||||||
 | 
								fixAfterErase();
 | 
				
			||||||
 | 
								validateState();
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
						}, field->lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//fixShadows();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Options::validateState() {
 | 
				
			||||||
 | 
						checkLastOption();
 | 
				
			||||||
 | 
						_valid = (ranges::count_if(_list, &Option::isGood) > 1);
 | 
				
			||||||
 | 
						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*) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rpl::producer<PollData> 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddSubsectionTitle(container, lng_polls_create_question);
 | 
				
			||||||
 | 
						const auto question = container->add(
 | 
				
			||||||
 | 
							object_ptr<Ui::InputField>(
 | 
				
			||||||
 | 
								container,
 | 
				
			||||||
 | 
								st::createPollField,
 | 
				
			||||||
 | 
								Ui::InputField::Mode::MultiLine,
 | 
				
			||||||
 | 
								langFactory(lng_polls_create_question_placeholder)),
 | 
				
			||||||
 | 
							st::createPollFieldPadding);
 | 
				
			||||||
 | 
						InitField(getDelegate()->outerContainer(), question);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return question;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
 | 
				
			||||||
 | 
						using namespace Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto id = rand_value<uint64>();
 | 
				
			||||||
 | 
						const auto valid = lifetime().make_state<rpl::event_stream<bool>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto result = object_ptr<Ui::VerticalLayout>(this);
 | 
				
			||||||
 | 
						const auto container = result.data();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto question = setupQuestion(container);
 | 
				
			||||||
 | 
						AddDivider(container);
 | 
				
			||||||
 | 
						AddSkip(container);
 | 
				
			||||||
 | 
						AddSubsectionTitle(container, lng_polls_create_options);
 | 
				
			||||||
 | 
						const auto options = lifetime().make_state<Options>(
 | 
				
			||||||
 | 
							getDelegate()->outerContainer(),
 | 
				
			||||||
 | 
							container);
 | 
				
			||||||
 | 
						auto limit = options->usedCount() | rpl::map([=](int count) {
 | 
				
			||||||
 | 
							return (count < kMaxOptionsCount)
 | 
				
			||||||
 | 
								? lng_polls_create_limit(lt_count, kMaxOptionsCount - count)
 | 
				
			||||||
 | 
								: lang(lng_polls_create_maximum);
 | 
				
			||||||
 | 
						}) | rpl::after_next([=] {
 | 
				
			||||||
 | 
							container->resizeToWidth(container->widthNoMargins());
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						container->add(
 | 
				
			||||||
 | 
							object_ptr<Ui::FlatLabel>(
 | 
				
			||||||
 | 
								container,
 | 
				
			||||||
 | 
								std::move(limit),
 | 
				
			||||||
 | 
								st::createPollLimitLabel),
 | 
				
			||||||
 | 
							st::createPollFieldPadding);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 = [=] {
 | 
				
			||||||
 | 
							auto result = PollData(id);
 | 
				
			||||||
 | 
							result.question = question->getLastText().trimmed();
 | 
				
			||||||
 | 
							result.answers = options->toPollAnswers();
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const auto updateValid = [=] {
 | 
				
			||||||
 | 
							valid->fire(isValidQuestion() && options->isValid());
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						valid->events(
 | 
				
			||||||
 | 
						) | rpl::distinct_until_changed(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](bool valid) {
 | 
				
			||||||
 | 
							clearButtons();
 | 
				
			||||||
 | 
							if (valid) {
 | 
				
			||||||
 | 
								addButton(
 | 
				
			||||||
 | 
									langFactory(lng_polls_create_button),
 | 
				
			||||||
 | 
									[=] { _submitRequests.fire(collectResult()); });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							addButton(langFactory(lng_cancel), [=] { closeBox(); });
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						options->isValidChanged(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=] {
 | 
				
			||||||
 | 
							updateValid();
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						options->scrollToWidget(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](not_null<QWidget*> widget) {
 | 
				
			||||||
 | 
							scrollToWidget(widget);
 | 
				
			||||||
 | 
						}, lifetime());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return std::move(result);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void CreatePollBox::prepare() {
 | 
				
			||||||
 | 
						setTitle(langFactory(lng_polls_create_title));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto inner = setInnerWidget(setupContent());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setDimensionsToContent(st::boxWideWidth, inner);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 "boxes/abstract_box.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Ui {
 | 
				
			||||||
 | 
					class VerticalLayout;
 | 
				
			||||||
 | 
					} // namespace Ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PollData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreatePollBox : public BoxContent {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						CreatePollBox(QWidget*);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rpl::producer<PollData> submitRequests() const;
 | 
				
			||||||
 | 
						void submitFailed(const QString &error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void setInnerFocus() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						void prepare() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						object_ptr<Ui::RpWidget> setupContent();
 | 
				
			||||||
 | 
						not_null<Ui::InputField*> setupQuestion(
 | 
				
			||||||
 | 
							not_null<Ui::VerticalLayout*> container);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Fn<void()> _setInnerFocus;
 | 
				
			||||||
 | 
						Fn<rpl::producer<bool>()> _dataIsValidValue;
 | 
				
			||||||
 | 
						rpl::event_stream<PollData> _submitRequests;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -125,3 +125,22 @@ bool PollData::applyResultToAnswers(
 | 
				
			||||||
bool PollData::voted() const {
 | 
					bool PollData::voted() const {
 | 
				
			||||||
	return ranges::find(answers, true, &PollAnswer::chosen) != end(answers);
 | 
						return ranges::find(answers, true, &PollAnswer::chosen) != end(answers);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MTPPoll PollDataToMTP(not_null<const PollData*> poll) {
 | 
				
			||||||
 | 
						const auto convert = [](const PollAnswer &answer) {
 | 
				
			||||||
 | 
							return MTP_pollAnswer(
 | 
				
			||||||
 | 
								MTP_string(answer.text),
 | 
				
			||||||
 | 
								MTP_bytes(answer.option));
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						auto answers = QVector<MTPPollAnswer>();
 | 
				
			||||||
 | 
						answers.reserve(poll->answers.size());
 | 
				
			||||||
 | 
						ranges::transform(
 | 
				
			||||||
 | 
							poll->answers,
 | 
				
			||||||
 | 
							ranges::back_inserter(answers),
 | 
				
			||||||
 | 
							convert);
 | 
				
			||||||
 | 
						return MTP_poll(
 | 
				
			||||||
 | 
							MTP_long(poll->id),
 | 
				
			||||||
 | 
							MTP_flags(MTPDpoll::Flag::f_closed),
 | 
				
			||||||
 | 
							MTP_string(poll->question),
 | 
				
			||||||
 | 
							MTP_vector<MTPPollAnswer>(answers));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,3 +48,5 @@ private:
 | 
				
			||||||
		bool isMinResults);
 | 
							bool isMinResults);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MTPPoll PollDataToMTP(not_null<const PollData*> poll);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -234,8 +234,8 @@ void ForceFullRepaint(not_null<QWidget*> widget) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PostponeCall(FnMut<void()> &&callable) {
 | 
					void PostponeCall(FnMut<void()> &&callable) {
 | 
				
			||||||
	const auto app = static_cast<Application*>(qApp);
 | 
						const auto application = static_cast<Application*>(qApp);
 | 
				
			||||||
	app->postponeCall(std::move(callable));
 | 
						application->postponeCall(std::move(callable));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace Ui
 | 
					} // namespace Ui
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2160,7 +2160,11 @@ void InputField::handleContentsChanged() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (tagsChanged || (_lastTextWithTags.text != currentText)) {
 | 
						if (tagsChanged || (_lastTextWithTags.text != currentText)) {
 | 
				
			||||||
		_lastTextWithTags.text = currentText;
 | 
							_lastTextWithTags.text = currentText;
 | 
				
			||||||
 | 
							const auto weak = make_weak(this);
 | 
				
			||||||
		emit changed();
 | 
							emit changed();
 | 
				
			||||||
 | 
							if (!weak) {
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		checkContentHeight();
 | 
							checkContentHeight();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	startPlaceholderAnimation();
 | 
						startPlaceholderAnimation();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,10 @@ class VerticalLayout : public RpWidget {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	using RpWidget::RpWidget;
 | 
						using RpWidget::RpWidget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int count() const {
 | 
				
			||||||
 | 
							return _rows.size();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	template <
 | 
						template <
 | 
				
			||||||
		typename Widget,
 | 
							typename Widget,
 | 
				
			||||||
		typename = std::enable_if_t<
 | 
							typename = std::enable_if_t<
 | 
				
			||||||
| 
						 | 
					@ -36,7 +40,7 @@ public:
 | 
				
			||||||
	Widget *add(
 | 
						Widget *add(
 | 
				
			||||||
			object_ptr<Widget> &&child,
 | 
								object_ptr<Widget> &&child,
 | 
				
			||||||
			const style::margins &margin = style::margins()) {
 | 
								const style::margins &margin = style::margins()) {
 | 
				
			||||||
		return insert(_rows.size(), std::move(child), margin);
 | 
							return insert(count(), std::move(child), margin);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QMargins getMargins() const override;
 | 
						QMargins getMargins() const override;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
				
			||||||
#include "boxes/mute_settings_box.h"
 | 
					#include "boxes/mute_settings_box.h"
 | 
				
			||||||
#include "boxes/add_contact_box.h"
 | 
					#include "boxes/add_contact_box.h"
 | 
				
			||||||
#include "boxes/report_box.h"
 | 
					#include "boxes/report_box.h"
 | 
				
			||||||
 | 
					#include "boxes/create_poll_box.h"
 | 
				
			||||||
#include "boxes/peer_list_controllers.h"
 | 
					#include "boxes/peer_list_controllers.h"
 | 
				
			||||||
#include "boxes/peers/manage_peer_box.h"
 | 
					#include "boxes/peers/manage_peer_box.h"
 | 
				
			||||||
#include "boxes/peers/edit_peer_info_box.h"
 | 
					#include "boxes/peers/edit_peer_info_box.h"
 | 
				
			||||||
| 
						 | 
					@ -360,6 +361,11 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
 | 
				
			||||||
				lang(lng_profile_add_participant),
 | 
									lang(lng_profile_add_participant),
 | 
				
			||||||
				[chat] { AddChatMembers(chat); });
 | 
									[chat] { AddChatMembers(chat); });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if (chat->canWrite()) {
 | 
				
			||||||
 | 
								_addAction(
 | 
				
			||||||
 | 
									lang(lng_polls_create),
 | 
				
			||||||
 | 
									[=] { PeerMenuCreatePoll(chat); });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		_addAction(
 | 
							_addAction(
 | 
				
			||||||
			lang(lng_profile_export_chat),
 | 
								lang(lng_profile_export_chat),
 | 
				
			||||||
			[=] { PeerMenuExportChat(chat); });
 | 
								[=] { PeerMenuExportChat(chat); });
 | 
				
			||||||
| 
						 | 
					@ -397,6 +403,11 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
 | 
				
			||||||
				lang(lng_channel_add_members),
 | 
									lang(lng_channel_add_members),
 | 
				
			||||||
				[channel] { PeerMenuAddChannelMembers(channel); });
 | 
									[channel] { PeerMenuAddChannelMembers(channel); });
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if (channel->canWrite()) {
 | 
				
			||||||
 | 
								_addAction(
 | 
				
			||||||
 | 
									lang(lng_polls_create),
 | 
				
			||||||
 | 
									[=] { PeerMenuCreatePoll(channel); });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		_addAction(
 | 
							_addAction(
 | 
				
			||||||
			lang(isGroup
 | 
								lang(isGroup
 | 
				
			||||||
				? lng_profile_export_chat
 | 
									? lng_profile_export_chat
 | 
				
			||||||
| 
						 | 
					@ -615,6 +626,19 @@ void PeerMenuShareContactBox(not_null<UserData*> user) {
 | 
				
			||||||
		}));
 | 
							}));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PeerMenuCreatePoll(not_null<PeerData*> peer) {
 | 
				
			||||||
 | 
						const auto box = Ui::show(Box<CreatePollBox>());
 | 
				
			||||||
 | 
						box->submitRequests(
 | 
				
			||||||
 | 
						) | rpl::start_with_next([=](const PollData &result) {
 | 
				
			||||||
 | 
							const auto options = ApiWrap::SendOptions(App::history(peer));
 | 
				
			||||||
 | 
							Auth().api().createPoll(result, options, crl::guard(box, [=] {
 | 
				
			||||||
 | 
								box->closeBox();
 | 
				
			||||||
 | 
							}), crl::guard(box, [=](const RPCError &error) {
 | 
				
			||||||
 | 
								box->submitFailed(lang(lng_attach_failed));
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
						}, box->lifetime());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QPointer<Ui::RpWidget> ShowForwardMessagesBox(
 | 
					QPointer<Ui::RpWidget> ShowForwardMessagesBox(
 | 
				
			||||||
		MessageIdsList &&items,
 | 
							MessageIdsList &&items,
 | 
				
			||||||
		FnMut<void()> &&successCallback) {
 | 
							FnMut<void()> &&successCallback) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,7 @@ void PeerMenuShareContactBox(not_null<UserData*> user);
 | 
				
			||||||
void PeerMenuAddContact(not_null<UserData*> user);
 | 
					void PeerMenuAddContact(not_null<UserData*> user);
 | 
				
			||||||
void PeerMenuAddChannelMembers(not_null<ChannelData*> channel);
 | 
					void PeerMenuAddChannelMembers(not_null<ChannelData*> channel);
 | 
				
			||||||
//void PeerMenuUngroupFeed(not_null<Data::Feed*> feed); // #feed
 | 
					//void PeerMenuUngroupFeed(not_null<Data::Feed*> feed); // #feed
 | 
				
			||||||
 | 
					void PeerMenuCreatePoll(not_null<PeerData*> peer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//void ToggleChannelGrouping(not_null<ChannelData*> channel, bool group); // #feed
 | 
					//void ToggleChannelGrouping(not_null<ChannelData*> channel, bool group); // #feed
 | 
				
			||||||
Fn<void()> ClearHistoryHandler(not_null<PeerData*> peer);
 | 
					Fn<void()> ClearHistoryHandler(not_null<PeerData*> peer);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,8 @@
 | 
				
			||||||
<(src_loc)/boxes/confirm_phone_box.h
 | 
					<(src_loc)/boxes/confirm_phone_box.h
 | 
				
			||||||
<(src_loc)/boxes/connection_box.cpp
 | 
					<(src_loc)/boxes/connection_box.cpp
 | 
				
			||||||
<(src_loc)/boxes/connection_box.h
 | 
					<(src_loc)/boxes/connection_box.h
 | 
				
			||||||
 | 
					<(src_loc)/boxes/create_poll_box.cpp
 | 
				
			||||||
 | 
					<(src_loc)/boxes/create_poll_box.h
 | 
				
			||||||
<(src_loc)/boxes/download_path_box.cpp
 | 
					<(src_loc)/boxes/download_path_box.cpp
 | 
				
			||||||
<(src_loc)/boxes/download_path_box.h
 | 
					<(src_loc)/boxes/download_path_box.h
 | 
				
			||||||
<(src_loc)/boxes/edit_caption_box.cpp
 | 
					<(src_loc)/boxes/edit_caption_box.cpp
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue