diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 55650ebfe..3288d109e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1836,6 +1836,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "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_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 diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 1be379a48..e63a4651a 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -4752,36 +4752,6 @@ void ApiWrap::sendExistingDocument( }; performRequest(); - AssertIsDebug(); - auto answers = QVector{ - 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()), - MTP_flags(0), - MTP_string("Very very very very very very very very very very " - "very very very long poll question text"), - MTP_vector(answers))), - MTP_string(captionText), - MTP_long(rand_value()), - MTPnullMarkup, - sentEntities - )).done([=](const MTPUpdates &result) { - applyUpdates(result); - }).fail( - base::duplicate(*failHandler) - ).afterRequest(history->sendRequestId - ).send(); - if (const auto main = App::main()) { main->finishForwarding(history); if (document->sticker()) { @@ -5344,6 +5314,45 @@ void ApiWrap::setSelfDestructDays(int days) { _selfDestructChanges.fire_copy(days); } +void ApiWrap::createPoll( + const PollData &data, + const SendOptions &options, + FnMut done, + FnMut 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()), + MTPReplyMarkup(), + MTPVector() + )).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( FullMsgId itemId, const std::vector &options) { @@ -5385,29 +5394,12 @@ void ApiWrap::closePoll(FullMsgId itemId) { return; } - const auto convert = [](const PollAnswer &answer) { - return MTP_pollAnswer( - MTP_string(answer.text), - MTP_bytes(answer.option)); - }; - - auto answers = QVector(); - answers.reserve(poll->answers.size()); - ranges::transform( - poll->answers, - ranges::back_inserter(answers), - convert); - const auto requestId = request(MTPmessages_EditMessage( MTP_flags(MTPmessages_EditMessage::Flag::f_media), item->history()->peer->input, MTP_int(item->id), MTPstring(), - MTP_inputMediaPoll(MTP_poll( - MTP_long(poll->id), - MTP_flags(MTPDpoll::Flag::f_closed), - MTP_string(poll->question), - MTP_vector(answers))), + MTP_inputMediaPoll(PollDataToMTP(poll)), MTPReplyMarkup(), MTPVector() )).done([=](const MTPUpdates &result) { diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 7f402c078..ded168558 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -377,6 +377,11 @@ public: rpl::producer selfDestructValue() const; void saveSelfDestruct(int days); + void createPoll( + const PollData &data, + const SendOptions &options, + FnMut done, + FnMut fail); void sendPollVotes( FullMsgId itemId, const std::vector &options); diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index ed5df5917..ca6b613af 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -408,7 +408,9 @@ bool Application::notify(QObject *receiver, QEvent *e) { _loopNestingLevel = _previousLoopNestingLevels.back(); _previousLoopNestingLevels.pop_back(); } - processPostponedCalls(--_eventNestingLevel); + const auto processTillLevel = _eventNestingLevel - 1; + processPostponedCalls(processTillLevel); + _eventNestingLevel = processTillLevel; return result; } diff --git a/Telegram/SourceFiles/boxes/boxes.style b/Telegram/SourceFiles/boxes/boxes.style index b23e242ae..418465494 100644 --- a/Telegram/SourceFiles/boxes/boxes.style +++ b/Telegram/SourceFiles/boxes/boxes.style @@ -834,3 +834,53 @@ themesScroll: ScrollArea(defaultScrollArea) { bottomsh: 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); diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp new file mode 100644 index 000000000..64b3394aa --- /dev/null +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -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 outer, + not_null container); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] rpl::producer isValidChanged() const; + [[nodiscard]] std::vector toPollAnswers() const; + void focusFirst(); + + [[nodiscard]] rpl::producer usedCount() const; + [[nodiscard]] rpl::producer> scrollToWidget() const; + +private: + class Option { + public: + static Option Create( + not_null outer, + not_null 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 field() const; + + [[nodiscard]] PollAnswer toPollAnswer(char id) const; + + [[nodiscard]] rpl::producer removeClicks() const; + + private: + Option() = default; + + base::unique_qptr _field; + base::unique_qptr _shadow; + base::unique_qptr _remove; + rpl::variable *_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 field) const; + + not_null _outer; + not_null _container; + int _position = 0; + std::vector