Support creating polls from bot keyboards.

This commit is contained in:
John Preston 2020-01-15 16:30:29 +03:00
parent c3aa2abe11
commit d0597407d8
10 changed files with 104 additions and 29 deletions

View File

@ -547,6 +547,7 @@ keyboardButtonGame#50f41ccf text:string = KeyboardButton;
keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton;
inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton; inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton;
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow; keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;

View File

@ -45,7 +45,8 @@ public:
Options( Options(
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,
bool chooseCorrectEnabled);
[[nodiscard]] bool isValid() const; [[nodiscard]] bool isValid() const;
[[nodiscard]] rpl::producer<bool> isValidChanged() const; [[nodiscard]] rpl::producer<bool> isValidChanged() const;
@ -128,6 +129,8 @@ private:
void destroy(std::unique_ptr<Option> option); void destroy(std::unique_ptr<Option> option);
void removeDestroyed(not_null<Option*> field); void removeDestroyed(not_null<Option*> field);
int findField(not_null<Ui::InputField*> field) const; int findField(not_null<Ui::InputField*> field) const;
[[nodiscard]] auto createChooseCorrectGroup()
-> std::shared_ptr<Ui::RadiobuttonGroup>;
not_null<QWidget*> _outer; not_null<QWidget*> _outer;
not_null<Ui::VerticalLayout*> _container; not_null<Ui::VerticalLayout*> _container;
@ -236,9 +239,11 @@ Options::Option::Option(
createRemove(); createRemove();
createWarning(); createWarning();
enableChooseCorrect(group); enableChooseCorrect(group);
_correctShown.stop();
if (_correct) { if (_correct) {
_correct->finishAnimating(); _correct->finishAnimating();
} }
updateFieldGeometry();
} }
bool Options::Option::hasShadow() const { bool Options::Option::hasShadow() const {
@ -449,10 +454,14 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
Options::Options( Options::Options(
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,
bool chooseCorrectEnabled)
: _outer(outer) : _outer(outer)
, _container(container) , _container(container)
, _session(session) , _session(session)
, _chooseCorrectGroup(chooseCorrectEnabled
? createChooseCorrectGroup()
: nullptr)
, _position(_container->count()) { , _position(_container->count()) {
checkLastOption(); checkLastOption();
} }
@ -518,15 +527,18 @@ void Options::focusFirst() {
_list.front()->setFocus(); _list.front()->setFocus();
} }
std::shared_ptr<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
result->setChangedCallback([=](int) {
validateState();
});
return result;
}
void Options::enableChooseCorrect(bool enabled) { void Options::enableChooseCorrect(bool enabled) {
_chooseCorrectGroup = enabled _chooseCorrectGroup = enabled
? std::make_shared<Ui::RadiobuttonGroup>(0) ? createChooseCorrectGroup()
: nullptr; : nullptr;
if (_chooseCorrectGroup) {
_chooseCorrectGroup->setChangedCallback([=](int) {
validateState();
});
}
validateState(); validateState();
for (auto &option : _list) { for (auto &option : _list) {
option->enableChooseCorrect(_chooseCorrectGroup); option->enableChooseCorrect(_chooseCorrectGroup);
@ -712,10 +724,12 @@ void Options::checkLastOption() {
CreatePollBox::CreatePollBox( CreatePollBox::CreatePollBox(
QWidget*, QWidget*,
not_null<Main::Session*> session, not_null<Main::Session*> session,
PublicVotes publicVotes, PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType) Api::SendType sendType)
: _session(session) : _session(session)
, _publicVotes(publicVotes) , _chosen(chosen)
, _disabled(disabled)
, _sendType(sendType) { , _sendType(sendType) {
} }
@ -792,7 +806,8 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
const auto options = lifetime().make_state<Options>( const auto options = lifetime().make_state<Options>(
getDelegate()->outerContainer(), getDelegate()->outerContainer(),
container, container,
_session); _session,
(_chosen & PollData::Flag::Quiz));
auto limit = options->usedCount() | rpl::after_next([=](int count) { auto limit = options->usedCount() | rpl::after_next([=](int count) {
setCloseByEscape(!count); setCloseByEscape(!count);
setCloseByOutsideClick(!count); setCloseByOutsideClick(!count);
@ -815,12 +830,12 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
AddSkip(container); AddSkip(container);
AddSubsectionTitle(container, tr::lng_polls_create_settings()); AddSubsectionTitle(container, tr::lng_polls_create_settings());
const auto anonymous = (_publicVotes == PublicVotes::Enabled) const auto anonymous = (!(_disabled & PollData::Flag::PublicVotes))
? container->add( ? container->add(
object_ptr<Ui::Checkbox>( object_ptr<Ui::Checkbox>(
container, container,
tr::lng_polls_create_anonymous(tr::now), tr::lng_polls_create_anonymous(tr::now),
true, !(_chosen & PollData::Flag::PublicVotes),
st::defaultCheckbox), st::defaultCheckbox),
st::createPollCheckboxMargin) st::createPollCheckboxMargin)
: nullptr; : nullptr;
@ -828,16 +843,19 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
object_ptr<Ui::Checkbox>( object_ptr<Ui::Checkbox>(
container, container,
tr::lng_polls_create_multiple_choice(tr::now), tr::lng_polls_create_multiple_choice(tr::now),
false, (_chosen & PollData::Flag::MultiChoice),
st::defaultCheckbox), st::defaultCheckbox),
st::createPollCheckboxMargin); st::createPollCheckboxMargin);
const auto quiz = container->add( const auto quiz = container->add(
object_ptr<Ui::Checkbox>( object_ptr<Ui::Checkbox>(
container, container,
tr::lng_polls_create_quiz_mode(tr::now), tr::lng_polls_create_quiz_mode(tr::now),
false, (_chosen & PollData::Flag::Quiz),
st::defaultCheckbox), st::defaultCheckbox),
st::createPollCheckboxMargin); st::createPollCheckboxMargin);
quiz->setDisabled(_disabled & PollData::Flag::Quiz);
multiple->setDisabled((_disabled & PollData::Flag::MultiChoice)
|| (_chosen & PollData::Flag::Quiz));
using namespace rpl::mappers; using namespace rpl::mappers;
quiz->checkedChanges( quiz->checkedChanges(
@ -845,14 +863,14 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
if (checked && multiple->checked()) { if (checked && multiple->checked()) {
multiple->setChecked(false); multiple->setChecked(false);
} }
multiple->setDisabled(checked); multiple->setDisabled(checked
|| (_disabled & PollData::Flag::MultiChoice));
options->enableChooseCorrect(checked); options->enableChooseCorrect(checked);
}, quiz->lifetime()); }, quiz->lifetime());
multiple->events( multiple->events(
) | rpl::filter([=](not_null<QEvent*> e) { ) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::MouseButtonPress) return (e->type() == QEvent::MouseButtonPress) && quiz->checked();
&& multiple->isDisabled();
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
Ui::Toast::Show("Quiz has only one right answer."); Ui::Toast::Show("Quiz has only one right answer.");
}, multiple->lifetime()); }, multiple->lifetime());

View File

@ -27,15 +27,12 @@ public:
PollData poll; PollData poll;
Api::SendOptions options; Api::SendOptions options;
}; };
enum class PublicVotes {
Enabled,
Disabled,
};
CreatePollBox( CreatePollBox(
QWidget*, QWidget*,
not_null<Main::Session*> session, not_null<Main::Session*> session,
PublicVotes publicVotes, PollData::Flags chosen,
PollData::Flags disabled,
Api::SendType sendType); Api::SendType sendType);
rpl::producer<Result> submitRequests() const; rpl::producer<Result> submitRequests() const;
@ -52,7 +49,8 @@ private:
not_null<Ui::VerticalLayout*> container); not_null<Ui::VerticalLayout*> container);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
const PublicVotes _publicVotes = PublicVotes(); const PollData::Flags _chosen = PollData::Flags();
const PollData::Flags _disabled = PollData::Flags();
const Api::SendType _sendType = Api::SendType(); const Api::SendType _sendType = Api::SendType();
Fn<void()> _setInnerFocus; Fn<void()> _setInnerFocus;
Fn<rpl::producer<bool>()> _dataIsValidValue; Fn<rpl::producer<bool>()> _dataIsValidValue;

View File

@ -751,6 +751,17 @@ int PeerData::slowmodeSecondsLeft() const {
return 0; return 0;
} }
bool PeerData::canSendPolls() const {
if (const auto user = asUser()) {
return user->isBot();
} else if (const auto chat = asChat()) {
return chat->canSendPolls();
} else if (const auto channel = asChannel()) {
return channel->canSendPolls();
}
return false;
}
namespace Data { namespace Data {
std::vector<ChatRestrictions> ListOfRestrictions() { std::vector<ChatRestrictions> ListOfRestrictions() {

View File

@ -191,6 +191,7 @@ public:
[[nodiscard]] bool canRevokeFullHistory() const; [[nodiscard]] bool canRevokeFullHistory() const;
[[nodiscard]] bool slowmodeApplied() const; [[nodiscard]] bool slowmodeApplied() const;
[[nodiscard]] int slowmodeSecondsLeft() const; [[nodiscard]] int slowmodeSecondsLeft() const;
[[nodiscard]] bool canSendPolls() const;
[[nodiscard]] UserData *asUser(); [[nodiscard]] UserData *asUser();
[[nodiscard]] const UserData *asUser() const; [[nodiscard]] const UserData *asUser() const;

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h" #include "core/application.h"
#include "media/clip/media_clip_reader.h" #include "media/clip/media_clip_reader.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "base/platform/base_platform_info.h" #include "base/platform/base_platform_info.h"
#include "data/data_peer.h" #include "data/data_peer.h"
@ -115,6 +116,19 @@ void activateBotCommand(
})); }));
} break; } break;
case ButtonType::RequestPoll: {
hideSingleUseKeyboard(msg);
auto chosen = PollData::Flags();
auto disabled = PollData::Flags();
if (!button->data.isEmpty()) {
disabled |= PollData::Flag::Quiz;
if (button->data[0]) {
chosen |= PollData::Flag::Quiz;
}
}
Window::PeerMenuCreatePoll(msg->history()->peer, chosen, disabled);
} break;
case ButtonType::SwitchInlineSame: case ButtonType::SwitchInlineSame:
case ButtonType::SwitchInline: { case ButtonType::SwitchInline: {
if (auto m = App::main()) { if (auto m = App::main()) {

View File

@ -843,6 +843,21 @@ void HistoryMessageReplyMarkup::createFromButtonRows(
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) { }, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
LOG(("API Error: inputKeyboardButtonUrlAuth received.")); LOG(("API Error: inputKeyboardButtonUrlAuth received."));
// Should not get those for the users. // Should not get those for the users.
}, [&](const MTPDkeyboardButtonRequestPoll &data) {
const auto quiz = [&] {
if (!data.vquiz()) {
return QByteArray();
}
return data.vquiz()->match([&](const MTPDboolTrue&) {
return QByteArray(1, 1);
}, [&](const MTPDboolFalse&) {
return QByteArray(1, 0);
});
}();
row.emplace_back(
Type::RequestPoll,
qs(data.vtext()),
quiz);
}); });
} }
if (!row.empty()) { if (!row.empty()) {

View File

@ -164,6 +164,7 @@ struct HistoryMessageMarkupButton {
Callback, Callback,
RequestPhone, RequestPhone,
RequestLocation, RequestLocation,
RequestPoll,
SwitchInline, SwitchInline,
SwitchInlineSame, SwitchInlineSame,
Game, Game,

View File

@ -413,6 +413,11 @@ void Filler::addUserActions(not_null<UserData*> user) {
tr::lng_profile_invite_to_group(tr::now), tr::lng_profile_invite_to_group(tr::now),
[=] { AddBotToGroup::Start(controller, user); }); [=] { AddBotToGroup::Start(controller, user); });
} }
if (user->canSendPolls()) {
_addAction(
tr::lng_polls_create(tr::now),
[=] { PeerMenuCreatePoll(user); });
}
if (user->canExportChatHistory()) { if (user->canExportChatHistory()) {
_addAction( _addAction(
tr::lng_profile_export_chat(tr::now), tr::lng_profile_export_chat(tr::now),
@ -707,12 +712,18 @@ void PeerMenuShareContactBox(
})); }));
} }
void PeerMenuCreatePoll(not_null<PeerData*> peer) { void PeerMenuCreatePoll(
not_null<PeerData*> peer,
PollData::Flags chosen,
PollData::Flags disabled) {
if (peer->isChannel() && !peer->isMegagroup()) {
chosen &= ~PollData::Flag::PublicVotes;
disabled |= PollData::Flag::PublicVotes;
}
const auto box = Ui::show(Box<CreatePollBox>( const auto box = Ui::show(Box<CreatePollBox>(
&peer->session(), &peer->session(),
((peer->isChannel() && !peer->isMegagroup()) chosen,
? CreatePollBox::PublicVotes::Disabled disabled,
: CreatePollBox::PublicVotes::Enabled),
Api::SendType::Normal)); Api::SendType::Normal));
const auto lock = box->lifetime().make_state<bool>(false); const auto lock = box->lifetime().make_state<bool>(false);
box->submitRequests( box->submitRequests(

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "data/data_poll.h"
class History; class History;
namespace Ui { namespace Ui {
@ -58,7 +60,10 @@ void PeerMenuAddChannelMembers(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<ChannelData*> channel); 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 PeerMenuCreatePoll(
not_null<PeerData*> peer,
PollData::Flags chosen = PollData::Flags(),
PollData::Flags disabled = PollData::Flags());
void PeerMenuBlockUserBox( void PeerMenuBlockUserBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Window::Controller*> window, not_null<Window::Controller*> window,