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;
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;
keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;

View File

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

View File

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

View File

@ -751,6 +751,17 @@ int PeerData::slowmodeSecondsLeft() const {
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 {
std::vector<ChatRestrictions> ListOfRestrictions() {

View File

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

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h"
#include "media/clip/media_clip_reader.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "history/history_item_components.h"
#include "base/platform/base_platform_info.h"
#include "data/data_peer.h"
@ -115,6 +116,19 @@ void activateBotCommand(
}));
} 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::SwitchInline: {
if (auto m = App::main()) {

View File

@ -843,6 +843,21 @@ void HistoryMessageReplyMarkup::createFromButtonRows(
}, [&](const MTPDinputKeyboardButtonUrlAuth &data) {
LOG(("API Error: inputKeyboardButtonUrlAuth received."));
// 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()) {

View File

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

View File

@ -413,6 +413,11 @@ void Filler::addUserActions(not_null<UserData*> user) {
tr::lng_profile_invite_to_group(tr::now),
[=] { AddBotToGroup::Start(controller, user); });
}
if (user->canSendPolls()) {
_addAction(
tr::lng_polls_create(tr::now),
[=] { PeerMenuCreatePoll(user); });
}
if (user->canExportChatHistory()) {
_addAction(
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>(
&peer->session(),
((peer->isChannel() && !peer->isMegagroup())
? CreatePollBox::PublicVotes::Disabled
: CreatePollBox::PublicVotes::Enabled),
chosen,
disabled,
Api::SendType::Normal));
const auto lock = box->lifetime().make_state<bool>(false);
box->submitRequests(

View File

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