Implement poll creation settings UI.

This commit is contained in:
John Preston 2020-01-10 23:07:21 +03:00
parent 989fad8554
commit 04d9b93e17
3 changed files with 301 additions and 131 deletions

View File

@ -2188,6 +2188,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_create_limit#one" = "You can add {count} more 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_limit#other" = "You can add {count} more options.";
"lng_polls_create_maximum" = "You have added the maximum number of options."; "lng_polls_create_maximum" = "You have added the maximum number of options.";
"lng_polls_create_settings" = "Settings";
"lng_polls_create_hint" = "Tap to select the right option";
"lng_polls_create_anonymous" = "Anonymous Votes";
"lng_polls_create_multiple_choice" = "Multiple Choice";
"lng_polls_create_quiz_mode" = "Quiz Mode";
"lng_polls_create_button" = "Create"; "lng_polls_create_button" = "Create";
"lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM."; "lng_outdated_title" = "PLEASE UPDATE YOUR OPERATING SYSTEM.";

View File

@ -812,15 +812,15 @@ createPollField: InputField(defaultInputField) {
} }
createPollFieldPadding: margins(22px, 5px, 22px, 5px); createPollFieldPadding: margins(22px, 5px, 22px, 5px);
createPollOptionField: InputField(createPollField) { createPollOptionField: InputField(createPollField) {
textMargins: margins(22px, 8px, 40px, 8px); textMargins: margins(22px, 11px, 40px, 11px);
placeholderMargins: margins(2px, 0px, 2px, 0px); placeholderMargins: margins(2px, 0px, 2px, 0px);
heightMax: 64px; heightMax: 68px;
} }
createPollLimitLabel: FlatLabel(defaultFlatLabel) { createPollLimitLabel: FlatLabel(defaultFlatLabel) {
minWidth: 274px; minWidth: 274px;
align: align(topleft); align: align(topleft);
} }
createPollLimitPadding: margins(22px, 10px, 22px, 5px); createPollLimitPadding: margins(22px, 10px, 22px, 16px);
createPollOptionRemove: CrossButton { createPollOptionRemove: CrossButton {
width: 22px; width: 22px;
height: 22px; height: 22px;
@ -841,7 +841,7 @@ createPollOptionRemove: CrossButton {
color: windowBgOver; color: windowBgOver;
} }
} }
createPollOptionRemovePosition: point(10px, 7px); createPollOptionRemovePosition: point(11px, 9px);
createPollWarning: FlatLabel(defaultFlatLabel) { createPollWarning: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
palette: TextPalette(defaultTextPalette) { palette: TextPalette(defaultTextPalette) {
@ -849,6 +849,8 @@ createPollWarning: FlatLabel(defaultFlatLabel) {
} }
} }
createPollWarningPosition: point(16px, 6px); createPollWarningPosition: point(16px, 6px);
createPollCheckboxMargin: margins(23px, 10px, 23px, 10px);
createPollFieldTitlePadding: margins(22px, 7px, 10px, 6px);
callSettingsButton: IconButton { callSettingsButton: IconButton {
width: 50px; width: 50px;

View File

@ -12,10 +12,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/toast/toast.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "chat_helpers/message_field.h" #include "chat_helpers/message_field.h"
@ -49,6 +52,8 @@ public:
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const; [[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
void focusFirst(); void focusFirst();
void enableChooseCorrect(bool enabled);
[[nodiscard]] rpl::producer<int> usedCount() const; [[nodiscard]] rpl::producer<int> usedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const; [[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
[[nodiscard]] rpl::producer<> backspaceInFront() const; [[nodiscard]] rpl::producer<> backspaceInFront() const;
@ -56,23 +61,31 @@ public:
private: private:
class Option { class Option {
public: public:
static Option Create( Option(
not_null<QWidget*> outer, not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Main::Session*> session,
int position); int position,
std::shared_ptr<Ui::RadiobuttonGroup> group);
Option(const Option &other) = delete;
Option &operator=(const Option &other) = delete;
void toggleRemoveAlways(bool toggled); void toggleRemoveAlways(bool toggled);
void enableChooseCorrect(
std::shared_ptr<Ui::RadiobuttonGroup> group);
void show(anim::type animated); void show(anim::type animated);
void destroy(FnMut<void()> done); void destroy(FnMut<void()> done);
//[[nodisacrd]] bool hasShadow() const; [[nodisacrd]] bool hasShadow() const;
//void destroyShadow(); void createShadow();
void destroyShadow();
[[nodiscard]] bool isEmpty() const; [[nodiscard]] bool isEmpty() const;
[[nodiscard]] bool isGood() const; [[nodiscard]] bool isGood() const;
[[nodiscard]] bool isTooLong() const; [[nodiscard]] bool isTooLong() const;
[[nodiscard]] bool isCorrect() const;
[[nodiscard]] bool hasFocus() const; [[nodiscard]] bool hasFocus() const;
void setFocus() const; void setFocus() const;
void clearValue(); void clearValue();
@ -86,29 +99,18 @@ private:
[[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const; [[nodiscard]] rpl::producer<Qt::MouseButton> removeClicks() const;
inline bool operator<(const Option &other) const {
return field() < other.field();
}
friend inline bool operator<(
const Option &option,
Ui::InputField *field) {
return option.field() < field;
}
friend inline bool operator<(
Ui::InputField *field,
const Option &option) {
return field < option.field();
}
private: private:
Option() = default;
void createShadow();
void createRemove(); void createRemove();
void createWarning(); void createWarning();
void toggleCorrectSpace(bool visible);
void updateFieldGeometry();
base::unique_qptr<Ui::SlideWrap<Ui::InputField>> _field; 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::PlainShadow> _shadow;
base::unique_qptr<Ui::CrossButton> _remove; base::unique_qptr<Ui::CrossButton> _remove;
rpl::variable<bool> *_removeAlways = nullptr; rpl::variable<bool> *_removeAlways = nullptr;
@ -116,23 +118,24 @@ private:
}; };
[[nodiscard]] bool full() const; [[nodiscard]] bool full() const;
//[[nodiscard]] bool correctShadows() const; [[nodiscard]] bool correctShadows() const;
//void fixShadows(); void fixShadows();
void removeEmptyTail(); void removeEmptyTail();
void addEmptyOption(); void addEmptyOption();
void checkLastOption(); void checkLastOption();
void validateState(); void validateState();
void fixAfterErase(); void fixAfterErase();
void destroy(Option &&option); void destroy(std::unique_ptr<Option> option);
void removeDestroyed(not_null<Ui::InputField*> field); void removeDestroyed(not_null<Option*> field);
int findField(not_null<Ui::InputField*> field) const; int findField(not_null<Ui::InputField*> field) const;
not_null<QWidget*> _outer; not_null<QWidget*> _outer;
not_null<Ui::VerticalLayout*> _container; not_null<Ui::VerticalLayout*> _container;
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
int _position = 0; int _position = 0;
std::vector<Option> _list; std::vector<std::unique_ptr<Option>> _list;
std::set<Option, std::less<>> _destroyed; std::vector<std::unique_ptr<Option>> _destroyed;
rpl::variable<bool> _valid = false; rpl::variable<bool> _valid = false;
rpl::variable<int> _usedCount = 0; rpl::variable<int> _usedCount = 0;
rpl::event_stream<not_null<QWidget*>> _scrollToWidget; rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
@ -187,58 +190,83 @@ void FocusAtEnd(not_null<Ui::InputField*> field) {
field->ensureCursorVisible(); field->ensureCursorVisible();
} }
Options::Option Options::Option::Create( Options::Option::Option(
not_null<QWidget*> outer, not_null<QWidget*> outer,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Main::Session*> session,
int position) { int position,
auto result = Option(); std::shared_ptr<Ui::RadiobuttonGroup> group)
const auto field = container->insert( : _wrap(container->insert(
position, position,
object_ptr<Ui::SlideWrap<Ui::InputField>>( object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
container, container,
object_ptr<Ui::InputField>( object_ptr<Ui::RpWidget>(container))))
container, , _content(_wrap->entity())
st::createPollOptionField, , _field(
Ui::InputField::Mode::NoNewlines, Ui::CreateChild<Ui::InputField>(
tr::lng_polls_create_option_add()))); _content.get(),
InitField(outer, field->entity(), session); st::createPollOptionField,
field->entity()->setMaxLength(kOptionLimit + kErrorLimit); Ui::InputField::Mode::NoNewlines,
result._field.reset(field); tr::lng_polls_create_option_add())) {
InitField(outer, _field, session);
_field->setMaxLength(kOptionLimit + kErrorLimit);
_field->show();
result.createShadow(); _wrap->hide(anim::type::instant);
result.createRemove();
result.createWarning(); _content->widthValue(
return result; ) | 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);
if (_correct) {
_correct->finishAnimating();
}
} }
//bool Options::Option::hasShadow() const { bool Options::Option::hasShadow() const {
// return (_shadow != nullptr); return (_shadow != nullptr);
//} }
void Options::Option::createShadow() { void Options::Option::createShadow() {
Expects(_field != nullptr); Expects(_content != nullptr);
if (_shadow) { if (_shadow) {
return; return;
} }
const auto value = Ui::CreateChild<Ui::PlainShadow>(field().get()); _shadow.reset(Ui::CreateChild<Ui::PlainShadow>(field().get()));
value->show(); _shadow->show();
field()->sizeValue( field()->sizeValue(
) | rpl::start_with_next([=](QSize size) { ) | rpl::start_with_next([=](QSize size) {
const auto left = st::createPollFieldPadding.left(); const auto left = st::createPollFieldPadding.left();
value->setGeometry( _shadow->setGeometry(
left, left,
size.height() - st::lineWidth, size.height() - st::lineWidth,
size.width() - left, size.width() - left,
st::lineWidth); st::lineWidth);
}, value->lifetime()); }, _shadow->lifetime());
_shadow.reset(value);
} }
//void Options::Option::destroyShadow() { void Options::Option::destroyShadow() {
// _shadow = nullptr; _shadow = nullptr;
//} }
void Options::Option::createRemove() { void Options::Option::createRemove() {
using namespace rpl::mappers; using namespace rpl::mappers;
@ -313,6 +341,10 @@ bool Options::Option::isTooLong() const {
return (field()->getLastText().size() > kOptionLimit); return (field()->getLastText().size() > kOptionLimit);
} }
bool Options::Option::isCorrect() const {
return isGood() && _correct && _correct->entity()->Checkbox::checked();
}
bool Options::Option::hasFocus() const { bool Options::Option::hasFocus() const {
return field()->hasFocus(); return field()->hasFocus();
} }
@ -333,8 +365,66 @@ void Options::Option::toggleRemoveAlways(bool toggled) {
*_removeAlways = 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 { not_null<Ui::InputField*> Options::Option::field() const {
return _field->entity(); return _field;
} }
void Options::Option::removePlaceholder() const { void Options::Option::removePlaceholder() const {
@ -344,10 +434,12 @@ void Options::Option::removePlaceholder() const {
PollAnswer Options::Option::toPollAnswer(int index) const { PollAnswer Options::Option::toPollAnswer(int index) const {
Expects(index >= 0 && index < kMaxOptionsCount); Expects(index >= 0 && index < kMaxOptionsCount);
return PollAnswer{ auto result = PollAnswer{
field()->getLastText().trimmed(), field()->getLastText().trimmed(),
QByteArray(1, ('0' + index)) QByteArray(1, ('0' + index))
}; };
result.correct = _correct ? _correct->entity()->Checkbox::checked() : false;
return result;
} }
rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const { rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
@ -390,19 +482,18 @@ rpl::producer<> Options::backspaceInFront() const {
} }
void Options::Option::show(anim::type animated) { void Options::Option::show(anim::type animated) {
_field->hide(anim::type::instant); _wrap->show(animated);
_field->show(animated);
} }
void Options::Option::destroy(FnMut<void()> done) { void Options::Option::destroy(FnMut<void()> done) {
if (anim::Disabled() || _field->isHidden()) { if (anim::Disabled() || _wrap->isHidden()) {
Ui::PostponeCall(std::move(done)); Ui::PostponeCall(std::move(done));
return; return;
} }
_field->hide(anim::type::normal); _wrap->hide(anim::type::normal);
base::call_delayed( base::call_delayed(
st::slideWrapDuration * 2, st::slideWrapDuration * 2,
_field.get(), _content.get(),
std::move(done)); std::move(done));
} }
@ -410,8 +501,8 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
auto result = std::vector<PollAnswer>(); auto result = std::vector<PollAnswer>();
result.reserve(_list.size()); result.reserve(_list.size());
auto counter = int(0); auto counter = int(0);
const auto makeAnswer = [&](const Option &option) { const auto makeAnswer = [&](const std::unique_ptr<Option> &option) {
return option.toPollAnswer(counter++); return option->toPollAnswer(counter++);
}; };
ranges::copy( ranges::copy(
_list _list
@ -424,29 +515,42 @@ std::vector<PollAnswer> Options::toPollAnswers() const {
void Options::focusFirst() { void Options::focusFirst() {
Expects(!_list.empty()); Expects(!_list.empty());
_list.front().setFocus(); _list.front()->setFocus();
}
void Options::enableChooseCorrect(bool enabled) {
_chooseCorrectGroup = enabled
? std::make_shared<Ui::RadiobuttonGroup>(0)
: nullptr;
if (_chooseCorrectGroup) {
_chooseCorrectGroup->setChangedCallback([=](int) {
validateState();
});
}
validateState();
for (auto &option : _list) {
option->enableChooseCorrect(_chooseCorrectGroup);
}
}
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();
} }
//
//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() { void Options::removeEmptyTail() {
// Only one option at the end of options list can be empty. // Only one option at the end of options list can be empty.
@ -465,7 +569,7 @@ void Options::removeEmptyTail() {
return; return;
} }
if (focusLast) { if (focusLast) {
emptyItem->setFocus(); (*emptyItem)->setFocus();
} }
for (auto i = emptyItem + 1; i != end; ++i) { for (auto i = emptyItem + 1; i != end; ++i) {
destroy(std::move(*i)); destroy(std::move(*i));
@ -474,44 +578,46 @@ void Options::removeEmptyTail() {
fixAfterErase(); fixAfterErase();
} }
void Options::destroy(Option &&option) { void Options::destroy(std::unique_ptr<Option> option) {
const auto field = option.field(); const auto value = option.get();
option.destroy([=] { removeDestroyed(field); }); option->destroy([=] { removeDestroyed(value); });
_destroyed.emplace(std::move(option)); _destroyed.push_back(std::move(option));
} }
void Options::fixAfterErase() { void Options::fixAfterErase() {
Expects(!_list.empty()); Expects(!_list.empty());
const auto last = _list.end() - 1; const auto last = _list.end() - 1;
last->setPlaceholder(); (*last)->setPlaceholder();
last->toggleRemoveAlways(false); (*last)->toggleRemoveAlways(false);
if (last != begin(_list)) { if (last != begin(_list)) {
(last - 1)->setPlaceholder(); (*(last - 1))->setPlaceholder();
(last - 1)->toggleRemoveAlways(false); (*(last - 1))->toggleRemoveAlways(false);
} }
fixShadows();
} }
void Options::addEmptyOption() { void Options::addEmptyOption() {
if (full()) { if (full()) {
return; return;
} else if (!_list.empty() && _list.back().isEmpty()) { } else if (!_list.empty() && _list.back()->isEmpty()) {
return; return;
} }
if (_list.size() > 1) { if (_list.size() > 1) {
(_list.end() - 2)->removePlaceholder(); (*(_list.end() - 2))->removePlaceholder();
(_list.end() - 2)->toggleRemoveAlways(true); (*(_list.end() - 2))->toggleRemoveAlways(true);
} }
_list.push_back(Option::Create( _list.push_back(std::make_unique<Option>(
_outer, _outer,
_container, _container,
_session, _session,
_position + _list.size() + _destroyed.size())); _position + _list.size() + _destroyed.size(),
const auto field = _list.back().field(); _chooseCorrectGroup));
const auto field = _list.back()->field();
QObject::connect(field, &Ui::InputField::submitted, [=] { QObject::connect(field, &Ui::InputField::submitted, [=] {
const auto index = findField(field); const auto index = findField(field);
if (_list[index].isGood() && index + 1 < _list.size()) { if (_list[index]->isGood() && index + 1 < _list.size()) {
_list[index + 1].setFocus(); _list[index + 1]->setFocus();
} }
}); });
QObject::connect(field, &Ui::InputField::changed, [=] { QObject::connect(field, &Ui::InputField::changed, [=] {
@ -534,25 +640,25 @@ void Options::addEmptyOption() {
const auto index = findField(field); const auto index = findField(field);
if (index > 0) { if (index > 0) {
_list[index - 1].setFocus(); _list[index - 1]->setFocus();
} else { } else {
_backspaceInFront.fire({}); _backspaceInFront.fire({});
} }
return base::EventFilterResult::Cancel; return base::EventFilterResult::Cancel;
}); });
_list.back().removeClicks( _list.back()->removeClicks(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
Ui::PostponeCall(crl::guard(field, [=] { Ui::PostponeCall(crl::guard(field, [=] {
Expects(!_list.empty()); Expects(!_list.empty());
const auto item = begin(_list) + findField(field); const auto item = begin(_list) + findField(field);
if (item == _list.end() - 1) { if (item == _list.end() - 1) {
item->clearValue(); (*item)->clearValue();
return; return;
} }
if (item->hasFocus()) { if ((*item)->hasFocus()) {
(item + 1)->setFocus(); (*(item + 1))->setFocus();
} }
destroy(std::move(*item)); destroy(std::move(*item));
_list.erase(item); _list.erase(item);
@ -561,21 +667,28 @@ void Options::addEmptyOption() {
})); }));
}, field->lifetime()); }, field->lifetime());
_list.back().show((_list.size() == 1) _list.back()->show((_list.size() == 1)
? anim::type::instant ? anim::type::instant
: anim::type::normal); : anim::type::normal);
//fixShadows(); fixShadows();
} }
void Options::removeDestroyed(not_null<Ui::InputField*> field) { void Options::removeDestroyed(not_null<Option*> option) {
_destroyed.erase(_destroyed.find(field)); const auto i = ranges::find(
_destroyed,
option.get(),
&std::unique_ptr<Option>::get);
Assert(i != end(_destroyed));
_destroyed.erase(i);
} }
void Options::validateState() { void Options::validateState() {
checkLastOption(); 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)); && (ranges::find_if(_list, &Option::isTooLong) == end(_list))
const auto lastEmpty = !_list.empty() && _list.back().isEmpty(); && (!_chooseCorrectGroup
|| ranges::find_if(_list, &Option::isCorrect) != end(_list));
const auto lastEmpty = !_list.empty() && _list.back()->isEmpty();
_usedCount = _list.size() - (lastEmpty ? 1 : 0); _usedCount = _list.size() - (lastEmpty ? 1 : 0);
} }
@ -668,7 +781,12 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
const auto question = setupQuestion(container); const auto question = setupQuestion(container);
AddDivider(container); AddDivider(container);
AddSkip(container); AddSkip(container);
AddSubsectionTitle(container, tr::lng_polls_create_options()); container->add(
object_ptr<Ui::FlatLabel>(
container,
tr::lng_polls_create_options(),
st::settingsSubsectionTitle),
st::createPollFieldTitlePadding);
const auto options = lifetime().make_state<Options>( const auto options = lifetime().make_state<Options>(
getDelegate()->outerContainer(), getDelegate()->outerContainer(),
container, container,
@ -684,11 +802,56 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
container->resizeToWidth(container->widthNoMargins()); container->resizeToWidth(container->widthNoMargins());
}); });
container->add( container->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::DividerLabel>(
container, container,
std::move(limit), object_ptr<Ui::FlatLabel>(
st::createPollLimitLabel), container,
st::createPollLimitPadding); std::move(limit),
st::boxDividerLabel),
st::createPollLimitPadding));
AddSkip(container);
AddSubsectionTitle(container, tr::lng_polls_create_settings());
const auto anonymous = container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_anonymous(tr::now),
true,
st::defaultCheckbox),
st::createPollCheckboxMargin);
const auto multiple = container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_multiple_choice(tr::now),
false,
st::defaultCheckbox),
st::createPollCheckboxMargin);
const auto quiz = container->add(
object_ptr<Ui::Checkbox>(
container,
tr::lng_polls_create_quiz_mode(tr::now),
false,
st::defaultCheckbox),
st::createPollCheckboxMargin);
using namespace rpl::mappers;
quiz->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
if (checked && multiple->checked()) {
multiple->setChecked(false);
}
multiple->setDisabled(checked);
options->enableChooseCorrect(checked);
}, quiz->lifetime());
multiple->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::MouseButtonPress)
&& multiple->isDisabled();
}) | rpl::start_with_next([=] {
Ui::Toast::Show("Quiz has only one right answer.");
}, multiple->lifetime());
const auto isValidQuestion = [=] { const auto isValidQuestion = [=] {
const auto text = question->getLastText().trimmed(); const auto text = question->getLastText().trimmed();