mirror of https://github.com/procxx/kepka.git
				
				
				
			Add question / options length warnings.
This commit is contained in:
		
							parent
							
								
									b6f7832745
								
							
						
					
					
						commit
						363f6cb329
					
				| 
						 | 
				
			
			@ -1066,6 +1066,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
"lng_in_dlg_file" = "File";
 | 
			
		||||
"lng_in_dlg_sticker" = "Sticker";
 | 
			
		||||
"lng_in_dlg_sticker_emoji" = "{emoji} Sticker";
 | 
			
		||||
"lng_in_dlg_poll" = "Poll";
 | 
			
		||||
 | 
			
		||||
"lng_ban_user" = "Ban User";
 | 
			
		||||
"lng_delete_all_from" = "Delete all from this user";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -863,6 +863,7 @@ createPollLimitLabel: FlatLabel(defaultFlatLabel) {
 | 
			
		|||
	minWidth: 274px;
 | 
			
		||||
	align: align(topleft);
 | 
			
		||||
}
 | 
			
		||||
createPollLimitPadding: margins(22px, 10px, 22px, 5px);
 | 
			
		||||
createPollOptionRemove: CrossButton {
 | 
			
		||||
	width: 22px;
 | 
			
		||||
	height: 22px;
 | 
			
		||||
| 
						 | 
				
			
			@ -884,3 +885,10 @@ createPollOptionRemove: CrossButton {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
createPollOptionRemovePosition: point(10px, 7px);
 | 
			
		||||
createPollWarning: FlatLabel(defaultFlatLabel) {
 | 
			
		||||
	textFg: windowSubTextFg;
 | 
			
		||||
	palette: TextPalette(defaultTextPalette) {
 | 
			
		||||
		linkFg: boxTextFgError;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
createPollWarningPosition: point(16px, 6px);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,10 +15,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
			
		|||
#include "ui/widgets/shadow.h"
 | 
			
		||||
#include "ui/widgets/labels.h"
 | 
			
		||||
#include "ui/widgets/buttons.h"
 | 
			
		||||
#include "core/event_filter.h"
 | 
			
		||||
#include "chat_helpers/emoji_suggestions_widget.h"
 | 
			
		||||
#include "settings/settings_common.h"
 | 
			
		||||
#include "base/unique_qptr.h"
 | 
			
		||||
#include "styles/style_boxes.h"
 | 
			
		||||
#include "styles/style_settings.h"
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +29,7 @@ constexpr auto kMaxOptionsCount = 10;
 | 
			
		|||
constexpr auto kOptionLimit = 100;
 | 
			
		||||
constexpr auto kWarnQuestionLimit = 80;
 | 
			
		||||
constexpr auto kWarnOptionLimit = 30;
 | 
			
		||||
constexpr auto kErrorLimit = 99;
 | 
			
		||||
 | 
			
		||||
class Options {
 | 
			
		||||
public:
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +44,7 @@ public:
 | 
			
		|||
 | 
			
		||||
	[[nodiscard]] rpl::producer<int> usedCount() const;
 | 
			
		||||
	[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
 | 
			
		||||
	[[nodiscard]] rpl::producer<> backspaceInFront() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	class Option {
 | 
			
		||||
| 
						 | 
				
			
			@ -50,15 +54,14 @@ private:
 | 
			
		|||
			not_null<Ui::VerticalLayout*> container,
 | 
			
		||||
			int position);
 | 
			
		||||
 | 
			
		||||
		[[nodisacrd]] bool hasShadow() const;
 | 
			
		||||
		void createShadow();
 | 
			
		||||
		//void destroyShadow();
 | 
			
		||||
 | 
			
		||||
		void createRemove();
 | 
			
		||||
		void toggleRemoveAlways(bool toggled);
 | 
			
		||||
 | 
			
		||||
		//[[nodisacrd]] bool hasShadow() const;
 | 
			
		||||
		//void destroyShadow();
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]] bool isEmpty() const;
 | 
			
		||||
		[[nodiscard]] bool isGood() const;
 | 
			
		||||
		[[nodiscard]] bool isTooLong() const;
 | 
			
		||||
		[[nodiscard]] bool hasFocus() const;
 | 
			
		||||
		void setFocus() const;
 | 
			
		||||
		void clearValue();
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +78,10 @@ private:
 | 
			
		|||
	private:
 | 
			
		||||
		Option() = default;
 | 
			
		||||
 | 
			
		||||
		void createShadow();
 | 
			
		||||
		void createRemove();
 | 
			
		||||
		void createWarning();
 | 
			
		||||
 | 
			
		||||
		base::unique_qptr<Ui::InputField> _field;
 | 
			
		||||
		base::unique_qptr<Ui::PlainShadow> _shadow;
 | 
			
		||||
		base::unique_qptr<Ui::CrossButton> _remove;
 | 
			
		||||
| 
						 | 
				
			
			@ -99,6 +106,7 @@ private:
 | 
			
		|||
	rpl::variable<bool> _valid = false;
 | 
			
		||||
	rpl::variable<int> _usedCount = 0;
 | 
			
		||||
	rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
 | 
			
		||||
	rpl::event_stream<> _backspaceInFront;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +118,38 @@ void InitField(
 | 
			
		|||
	Ui::Emoji::SuggestionsController::Init(container, field);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(),
 | 
			
		||||
		Ui::FlatLabel::InitType::Simple,
 | 
			
		||||
		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 Options::Option::Create(
 | 
			
		||||
		not_null<QWidget*> outer,
 | 
			
		||||
		not_null<Ui::VerticalLayout*> container,
 | 
			
		||||
| 
						 | 
				
			
			@ -123,16 +163,18 @@ Options::Option Options::Option::Create(
 | 
			
		|||
			Ui::InputField::Mode::MultiLine,
 | 
			
		||||
			langFactory(lng_polls_create_option_add)));
 | 
			
		||||
	InitField(outer, field);
 | 
			
		||||
	field->setMaxLength(kOptionLimit + kErrorLimit);
 | 
			
		||||
	result._field.reset(field);
 | 
			
		||||
 | 
			
		||||
	result.createShadow();
 | 
			
		||||
	result.createRemove();
 | 
			
		||||
	result.createWarning();
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Options::Option::hasShadow() const {
 | 
			
		||||
	return (_shadow != nullptr);
 | 
			
		||||
}
 | 
			
		||||
//bool Options::Option::hasShadow() const {
 | 
			
		||||
//	return (_shadow != nullptr);
 | 
			
		||||
//}
 | 
			
		||||
 | 
			
		||||
void Options::Option::createShadow() {
 | 
			
		||||
	Expects(_field != nullptr);
 | 
			
		||||
| 
						 | 
				
			
			@ -195,13 +237,40 @@ void Options::Option::createRemove() {
 | 
			
		|||
	_remove.reset(remove);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Options::Option::createWarning() {
 | 
			
		||||
	using namespace rpl::mappers;
 | 
			
		||||
 | 
			
		||||
	const auto field = _field.get();
 | 
			
		||||
	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 {
 | 
			
		||||
	const auto text = _field->getLastText().trimmed();
 | 
			
		||||
	return !text.isEmpty() && (text.size() <= kOptionLimit);
 | 
			
		||||
	return !_field->getLastText().trimmed().isEmpty() && !isTooLong();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Options::Option::isTooLong() const {
 | 
			
		||||
	return (_field->getLastText().size() > kOptionLimit);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Options::Option::hasFocus() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -209,7 +278,7 @@ bool Options::Option::hasFocus() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void Options::Option::setFocus() const {
 | 
			
		||||
	_field->setFocus();
 | 
			
		||||
	FocusAtEnd(_field);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Options::Option::clearValue() {
 | 
			
		||||
| 
						 | 
				
			
			@ -272,6 +341,10 @@ rpl::producer<not_null<QWidget*>> Options::scrollToWidget() const {
 | 
			
		|||
	return _scrollToWidget.events();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
rpl::producer<> Options::backspaceInFront() const {
 | 
			
		||||
	return _backspaceInFront.events();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PollAnswer> Options::toPollAnswers() const {
 | 
			
		||||
	auto result = std::vector<PollAnswer>();
 | 
			
		||||
	result.reserve(_list.size());
 | 
			
		||||
| 
						 | 
				
			
			@ -378,6 +451,24 @@ void Options::addEmptyOption() {
 | 
			
		|||
	QObject::connect(field, &Ui::InputField::focused, [=] {
 | 
			
		||||
		_scrollToWidget.fire_copy(field);
 | 
			
		||||
	});
 | 
			
		||||
	Core::InstallEventFilter(field, [=](not_null<QEvent*> event) {
 | 
			
		||||
		if (event->type() != QEvent::KeyPress
 | 
			
		||||
			|| !field->getLastText().isEmpty()) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		const auto key = static_cast<QKeyEvent*>(event.get())->key();
 | 
			
		||||
		if (key != Qt::Key_Backspace) {
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto index = findField(field);
 | 
			
		||||
		if (index > 0) {
 | 
			
		||||
			_list[index - 1].setFocus();
 | 
			
		||||
		} else {
 | 
			
		||||
			_backspaceInFront.fire({});
 | 
			
		||||
		}
 | 
			
		||||
		return true;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	_list.back().removeClicks(
 | 
			
		||||
	) | rpl::start_with_next([=] {
 | 
			
		||||
| 
						 | 
				
			
			@ -403,7 +494,8 @@ void Options::addEmptyOption() {
 | 
			
		|||
 | 
			
		||||
void Options::validateState() {
 | 
			
		||||
	checkLastOption();
 | 
			
		||||
	_valid = (ranges::count_if(_list, &Option::isGood) > 1);
 | 
			
		||||
	_valid = (ranges::count_if(_list, &Option::isGood) > 1)
 | 
			
		||||
		&& (ranges::find_if(_list, &Option::isTooLong) == end(_list));
 | 
			
		||||
	const auto lastEmpty = !_list.empty() && _list.back().isEmpty();
 | 
			
		||||
	_usedCount = _list.size() - (lastEmpty ? 1 : 0);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -454,6 +546,29 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
 | 
			
		|||
			langFactory(lng_polls_create_question_placeholder)),
 | 
			
		||||
		st::createPollFieldPadding);
 | 
			
		||||
	InitField(getDelegate()->outerContainer(), question);
 | 
			
		||||
	question->setMaxLength(kQuestionLimit + kErrorLimit);
 | 
			
		||||
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -486,7 +601,7 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
 | 
			
		|||
			container,
 | 
			
		||||
			std::move(limit),
 | 
			
		||||
			st::createPollLimitLabel),
 | 
			
		||||
		st::createPollFieldPadding);
 | 
			
		||||
		st::createPollLimitPadding);
 | 
			
		||||
 | 
			
		||||
	const auto isValidQuestion = [=] {
 | 
			
		||||
		const auto text = question->getLastText().trimmed();
 | 
			
		||||
| 
						 | 
				
			
			@ -512,7 +627,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
 | 
			
		|||
	const auto updateValid = [=] {
 | 
			
		||||
		valid->fire(isValidQuestion() && options->isValid());
 | 
			
		||||
	};
 | 
			
		||||
	valid->events(
 | 
			
		||||
	connect(question, &Ui::InputField::changed, [=] {
 | 
			
		||||
		updateValid();
 | 
			
		||||
	});
 | 
			
		||||
	valid->events_starting_with(
 | 
			
		||||
		false
 | 
			
		||||
	) | rpl::distinct_until_changed(
 | 
			
		||||
	) | rpl::start_with_next([=](bool valid) {
 | 
			
		||||
		clearButtons();
 | 
			
		||||
| 
						 | 
				
			
			@ -534,6 +653,11 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
 | 
			
		|||
		scrollToWidget(widget);
 | 
			
		||||
	}, lifetime());
 | 
			
		||||
 | 
			
		||||
	options->backspaceInFront(
 | 
			
		||||
	) | rpl::start_with_next([=] {
 | 
			
		||||
		FocusAtEnd(question);
 | 
			
		||||
	}, lifetime());
 | 
			
		||||
 | 
			
		||||
	return std::move(result);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -543,4 +667,7 @@ void CreatePollBox::prepare() {
 | 
			
		|||
	const auto inner = setInnerWidget(setupContent());
 | 
			
		||||
 | 
			
		||||
	setDimensionsToContent(st::boxWideWidth, inner);
 | 
			
		||||
 | 
			
		||||
	setCloseByEscape(false);
 | 
			
		||||
	setCloseByOutsideClick(false);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1216,12 +1216,8 @@ PollData *MediaPoll::poll() const {
 | 
			
		|||
	return _poll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString MediaPoll::chatsListText() const {
 | 
			
		||||
	return QString(); // #TODO polls
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString MediaPoll::notificationText() const {
 | 
			
		||||
	return QString(); // #TODO polls
 | 
			
		||||
	return lang(lng_in_dlg_poll);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QString MediaPoll::pinnedTextSubstring() const {
 | 
			
		||||
| 
						 | 
				
			
			@ -1229,7 +1225,19 @@ QString MediaPoll::pinnedTextSubstring() const {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
TextWithEntities MediaPoll::clipboardText() const {
 | 
			
		||||
	return TextWithEntities(); // #TODO polls
 | 
			
		||||
	const auto text = qsl("[ ")
 | 
			
		||||
		+ lang(lng_in_dlg_poll)
 | 
			
		||||
		+ qsl(" : ")
 | 
			
		||||
		+ _poll->question
 | 
			
		||||
		+ qsl(" ]")
 | 
			
		||||
		+ ranges::accumulate(
 | 
			
		||||
			ranges::view::all(
 | 
			
		||||
				_poll->answers
 | 
			
		||||
			) | ranges::view::transform(
 | 
			
		||||
				[](const PollAnswer &answer) { return "\n- " + answer.text; }
 | 
			
		||||
			),
 | 
			
		||||
			QString());
 | 
			
		||||
	return { text, EntitiesInText() };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MediaPoll::updateInlineResultMedia(const MTPMessageMedia &media) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -390,7 +390,6 @@ public:
 | 
			
		|||
 | 
			
		||||
	PollData *poll() const override;
 | 
			
		||||
 | 
			
		||||
	QString chatsListText() const override;
 | 
			
		||||
	QString notificationText() const override;
 | 
			
		||||
	QString pinnedTextSubstring() const override;
 | 
			
		||||
	TextWithEntities clipboardText() const override;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue