Added ability to insert custom emoji to polls.

This commit is contained in:
23rd 2024-04-19 02:23:55 +03:00 committed by John Preston
parent e6c22ec1ca
commit c803603de4
3 changed files with 235 additions and 40 deletions

View file

@ -691,6 +691,10 @@ createPollOptionField: InputField(createPollField) {
placeholderMargins: margins(2px, 0px, 2px, 0px); placeholderMargins: margins(2px, 0px, 2px, 0px);
heightMax: 68px; heightMax: 68px;
} }
createPollOptionFieldPremium: InputField(createPollOptionField) {
textMargins: margins(22px, 11px, 68px, 11px);
}
createPollOptionFieldPremiumEmojiPosition: point(15px, -1px);
createPollSolutionField: InputField(createPollField) { createPollSolutionField: InputField(createPollField) {
textMargins: margins(0px, 4px, 0px, 4px); textMargins: margins(0px, 4px, 0px, 4px);
border: 1px; border: 1px;
@ -704,7 +708,7 @@ createPollOptionRemove: CrossButton {
cross: CrossAnimation { cross: CrossAnimation {
size: 22px; size: 22px;
skip: 6px; skip: 6px;
stroke: 1.; stroke: 1.5;
minScale: 0.3; minScale: 0.3;
} }
crossFg: boxTitleCloseFg; crossFg: boxTitleCloseFg;
@ -718,6 +722,7 @@ createPollOptionRemove: CrossButton {
} }
} }
createPollOptionRemovePosition: point(11px, 9px); createPollOptionRemovePosition: point(11px, 9px);
createPollOptionEmojiPositionSkip: 4px;
createPollWarning: FlatLabel(defaultFlatLabel) { createPollWarning: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg; textFg: windowSubTextFg;
palette: TextPalette(defaultTextPalette) { palette: TextPalette(defaultTextPalette) {

View file

@ -7,33 +7,41 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "boxes/create_poll_box.h" #include "boxes/create_poll_box.h"
#include "lang/lang_keys.h" #include "base/call_delayed.h"
#include "data/data_poll.h" #include "base/event_filter.h"
#include "ui/toast/toast.h" #include "base/random.h"
#include "ui/wrap/vertical_layout.h" #include "base/unique_qptr.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/shadow.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/text/text_utilities.h"
#include "ui/vertical_list.h"
#include "main/main_session.h"
#include "core/application.h"
#include "core/core_settings.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"
#include "menu/menu_send.h" #include "chat_helpers/tabbed_panel.h"
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_poll.h"
#include "data/data_user.h"
#include "data/stickers/data_custom_emoji.h"
#include "history/view/history_view_schedule_box.h" #include "history/view/history_view_schedule_box.h"
#include "base/unique_qptr.h" #include "lang/lang_keys.h"
#include "base/event_filter.h" #include "main/main_session.h"
#include "base/call_delayed.h" #include "menu/menu_send.h"
#include "base/random.h" #include "ui/controls/emoji_button.h"
#include "ui/rect.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/shadow.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h" // defaultComposeFiles.
#include "styles/style_layers.h"
#include "styles/style_settings.h"
namespace { namespace {
@ -46,12 +54,107 @@ constexpr auto kSolutionLimit = 200;
constexpr auto kWarnSolutionLimit = 60; constexpr auto kWarnSolutionLimit = 60;
constexpr auto kErrorLimit = 99; constexpr auto kErrorLimit = 99;
[[nodiscard]] not_null<Ui::EmojiButton*> AddEmojiToggleToField(
not_null<Ui::InputField*> field,
not_null<Ui::BoxContent*> box,
not_null<Window::SessionController*> controller,
not_null<ChatHelpers::TabbedPanel*> emojiPanel,
QPoint shift) {
const auto emojiToggle = Ui::CreateChild<Ui::EmojiButton>(
field->parentWidget(),
st::defaultComposeFiles.emoji);
const auto fade = Ui::CreateChild<Ui::FadeAnimation>(
emojiToggle,
emojiToggle,
0.5);
{
const auto fadeTarget = Ui::CreateChild<Ui::RpWidget>(emojiToggle);
fadeTarget->resize(emojiToggle->size());
fadeTarget->paintRequest(
) | rpl::start_with_next([=](const QRect &rect) {
auto p = QPainter(fadeTarget);
if (fade->animating()) {
p.fillRect(fadeTarget->rect(), st::boxBg);
}
fade->paint(p);
}, fadeTarget->lifetime());
rpl::single(false) | rpl::then(
field->focusedChanges()
) | rpl::start_with_next([=](bool shown) {
if (shown) {
fade->fadeIn(st::universalDuration);
} else {
fade->fadeOut(st::universalDuration);
}
}, emojiToggle->lifetime());
fade->fadeOut(1);
fade->finish();
}
const auto outer = box->getDelegate()->outerContainer();
const auto allow = [](not_null<DocumentData*>) { return true; };
InitMessageFieldHandlers(
controller,
field,
Window::GifPauseReason::Layer,
allow);
Ui::Emoji::SuggestionsController::Init(
outer,
field,
&controller->session(),
Ui::Emoji::SuggestionsController::Options{
.suggestCustomEmoji = true,
.allowCustomWithoutPremium = allow,
});
const auto updateEmojiPanelGeometry = [=] {
const auto parent = emojiPanel->parentWidget();
const auto global = emojiToggle->mapToGlobal({ 0, 0 });
const auto local = parent->mapFromGlobal(global);
const auto right = local.x() + emojiToggle->width() * 3;
const auto isDropDown = local.y() < parent->height() / 2;
emojiPanel->setDropDown(isDropDown);
if (isDropDown) {
emojiPanel->moveTopRight(
local.y() + emojiToggle->height(),
right);
} else {
emojiPanel->moveBottomRight(local.y(), right);
}
};
rpl::combine(
box->sizeValue(),
field->geometryValue()
) | rpl::start_with_next([=](QSize outer, QRect inner) {
emojiToggle->moveToLeft(
rect::right(inner) + shift.x(),
inner.y() + shift.y());
emojiToggle->update();
}, emojiToggle->lifetime());
emojiToggle->installEventFilter(emojiPanel);
emojiToggle->addClickHandler([=] {
updateEmojiPanelGeometry();
emojiPanel->toggleAnimated();
});
const auto filterCallback = [=](not_null<QEvent*> event) {
if (event->type() == QEvent::Enter) {
updateEmojiPanelGeometry();
}
return base::EventFilterResult::Continue;
};
base::install_event_filter(emojiToggle, filterCallback);
return emojiToggle;
}
class Options { class Options {
public: public:
Options( Options(
not_null<QWidget*> outer, not_null<Ui::BoxContent*> box,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Window::SessionController*> controller,
ChatHelpers::TabbedPanel *emojiPanel,
bool chooseCorrectEnabled); bool chooseCorrectEnabled);
[[nodiscard]] bool hasOptions() const; [[nodiscard]] bool hasOptions() const;
@ -140,9 +243,10 @@ private:
[[nodiscard]] auto createChooseCorrectGroup() [[nodiscard]] auto createChooseCorrectGroup()
-> std::shared_ptr<Ui::RadiobuttonGroup>; -> std::shared_ptr<Ui::RadiobuttonGroup>;
not_null<QWidget*> _outer; not_null<Ui::BoxContent*> _box;
not_null<Ui::VerticalLayout*> _container; not_null<Ui::VerticalLayout*> _container;
const not_null<Main::Session*> _session; const not_null<Window::SessionController*> _controller;
ChatHelpers::TabbedPanel * const _emojiPanel;
std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup; std::shared_ptr<Ui::RadiobuttonGroup> _chooseCorrectGroup;
int _position = 0; int _position = 0;
std::vector<std::unique_ptr<Option>> _list; std::vector<std::unique_ptr<Option>> _list;
@ -154,6 +258,7 @@ private:
rpl::event_stream<not_null<QWidget*>> _scrollToWidget; rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
rpl::event_stream<> _backspaceInFront; rpl::event_stream<> _backspaceInFront;
rpl::event_stream<> _tabbed; rpl::event_stream<> _tabbed;
rpl::lifetime _emojiPanelLifetime;
}; };
@ -193,8 +298,9 @@ not_null<Ui::FlatLabel*> CreateWarningLabel(
if (value >= 0) { if (value >= 0) {
result->setText(QString::number(value)); result->setText(QString::number(value));
} else { } else {
constexpr auto kMinus = QChar(0x2212);
result->setMarkedText(Ui::Text::Colorized( result->setMarkedText(Ui::Text::Colorized(
QString::number(value))); kMinus + QString::number(std::abs(value))));
} }
result->setVisible(shown); result->setVisible(shown);
})); }));
@ -223,7 +329,9 @@ Options::Option::Option(
, _field( , _field(
Ui::CreateChild<Ui::InputField>( Ui::CreateChild<Ui::InputField>(
_content.get(), _content.get(),
st::createPollOptionField, session->user()->isPremium()
? st::createPollOptionFieldPremium
: st::createPollOptionField,
Ui::InputField::Mode::NoNewlines, Ui::InputField::Mode::NoNewlines,
tr::lng_polls_create_option_add())) { tr::lng_polls_create_option_add())) {
InitField(outer, _field, session); InitField(outer, _field, session);
@ -299,7 +407,7 @@ void Options::Option::createRemove() {
const auto remove = Ui::CreateChild<Ui::CrossButton>( const auto remove = Ui::CreateChild<Ui::CrossButton>(
field.get(), field.get(),
st::createPollOptionRemove); st::createPollOptionRemove);
remove->hide(anim::type::instant); remove->show(anim::type::instant);
const auto toggle = lifetime.make_state<rpl::variable<bool>>(false); const auto toggle = lifetime.make_state<rpl::variable<bool>>(false);
_removeAlways = lifetime.make_state<rpl::variable<bool>>(false); _removeAlways = lifetime.make_state<rpl::variable<bool>>(false);
@ -309,6 +417,7 @@ void Options::Option::createRemove() {
// Don't capture 'this'! Because Option is a value type. // Don't capture 'this'! Because Option is a value type.
*toggle = !field->getLastText().isEmpty(); *toggle = !field->getLastText().isEmpty();
}, field->lifetime()); }, field->lifetime());
#if 0
rpl::combine( rpl::combine(
toggle->value(), toggle->value(),
_removeAlways->value(), _removeAlways->value(),
@ -316,6 +425,7 @@ void Options::Option::createRemove() {
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
remove->toggle(shown, anim::type::normal); remove->toggle(shown, anim::type::normal);
}, remove->lifetime()); }, remove->lifetime());
#endif
field->widthValue( field->widthValue(
) | rpl::start_with_next([=](int width) { ) | rpl::start_with_next([=](int width) {
@ -475,13 +585,15 @@ rpl::producer<Qt::MouseButton> Options::Option::removeClicks() const {
} }
Options::Options( Options::Options(
not_null<QWidget*> outer, not_null<Ui::BoxContent*> box,
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
not_null<Main::Session*> session, not_null<Window::SessionController*> controller,
ChatHelpers::TabbedPanel *emojiPanel,
bool chooseCorrectEnabled) bool chooseCorrectEnabled)
: _outer(outer) : _box(box)
, _container(container) , _container(container)
, _session(session) , _controller(controller)
, _emojiPanel(emojiPanel)
, _chooseCorrectGroup(chooseCorrectEnabled , _chooseCorrectGroup(chooseCorrectEnabled
? createChooseCorrectGroup() ? createChooseCorrectGroup()
: nullptr) : nullptr)
@ -651,12 +763,40 @@ void Options::addEmptyOption() {
(*(_list.end() - 2))->toggleRemoveAlways(true); (*(_list.end() - 2))->toggleRemoveAlways(true);
} }
_list.push_back(std::make_unique<Option>( _list.push_back(std::make_unique<Option>(
_outer, _box,
_container, _container,
_session, &_controller->session(),
_position + _list.size() + _destroyed.size(), _position + _list.size() + _destroyed.size(),
_chooseCorrectGroup)); _chooseCorrectGroup));
const auto field = _list.back()->field(); const auto field = _list.back()->field();
if (const auto emojiPanel = _emojiPanel) {
const auto emojiToggle = AddEmojiToggleToField(
field,
_box,
_controller,
emojiPanel,
QPoint(
-st::createPollOptionFieldPremium.textMargins.right(),
st::createPollOptionEmojiPositionSkip));
emojiToggle->shownValue() | rpl::start_with_next([=](bool shown) {
if (!shown) {
return;
}
_emojiPanelLifetime.destroy();
emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
if (field->hasFocus()) {
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
}
}, _emojiPanelLifetime);
emojiPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
if (field->hasFocus()) {
Data::InsertCustomEmoji(field, data.document);
}
}, _emojiPanelLifetime);
}, emojiToggle->lifetime());
}
field->submits( field->submits(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto index = findField(field); const auto index = findField(field);
@ -703,7 +843,7 @@ void Options::addEmptyOption() {
}); });
_list.back()->removeClicks( _list.back()->removeClicks(
) | rpl::take(1) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
Ui::PostponeCall(crl::guard(field, [=] { Ui::PostponeCall(crl::guard(field, [=] {
Expects(!_list.empty()); Expects(!_list.empty());
@ -795,19 +935,63 @@ not_null<Ui::InputField*> CreatePollBox::setupQuestion(
using namespace Settings; using namespace Settings;
const auto session = &_controller->session(); const auto session = &_controller->session();
const auto isPremium = session->user()->isPremium();
Ui::AddSubsectionTitle(container, tr::lng_polls_create_question()); Ui::AddSubsectionTitle(container, tr::lng_polls_create_question());
const auto question = container->add( const auto question = container->add(
object_ptr<Ui::InputField>( object_ptr<Ui::InputField>(
container, container,
st::createPollField, st::createPollField,
Ui::InputField::Mode::MultiLine, Ui::InputField::Mode::MultiLine,
tr::lng_polls_create_question_placeholder()), tr::lng_polls_create_question_placeholder()),
st::createPollFieldPadding); st::createPollFieldPadding
+ (isPremium
? QMargins(0, 0, st::defaultComposeFiles.emoji.inner.width, 0)
: QMargins()));
InitField(getDelegate()->outerContainer(), question, session); InitField(getDelegate()->outerContainer(), question, session);
question->setMaxLength(kQuestionLimit + kErrorLimit); question->setMaxLength(kQuestionLimit + kErrorLimit);
question->setSubmitSettings(Ui::InputField::SubmitSettings::Both); question->setSubmitSettings(Ui::InputField::SubmitSettings::Both);
question->customTab(true); question->customTab(true);
if (isPremium) {
using Selector = ChatHelpers::TabbedSelector;
const auto outer = getDelegate()->outerContainer();
_emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
outer,
_controller,
object_ptr<Selector>(
nullptr,
_controller->uiShow(),
Window::GifPauseReason::Layer,
Selector::Mode::EmojiOnly));
const auto emojiPanel = _emojiPanel.get();
emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(session->user());
const auto emojiToggle = AddEmojiToggleToField(
question,
this,
_controller,
emojiPanel,
st::createPollOptionFieldPremiumEmojiPosition);
emojiPanel->selector()->emojiChosen(
) | rpl::start_with_next([=](ChatHelpers::EmojiChosen data) {
if (question->hasFocus()) {
Ui::InsertEmojiAtCursor(question->textCursor(), data.emoji);
}
}, emojiToggle->lifetime());
emojiPanel->selector()->customEmojiChosen(
) | rpl::start_with_next([=](ChatHelpers::FileChosen data) {
if (question->hasFocus()) {
Data::InsertCustomEmoji(question, data.document);
}
}, emojiToggle->lifetime());
}
const auto warning = CreateWarningLabel( const auto warning = CreateWarningLabel(
container, container,
question, question,
@ -916,9 +1100,10 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
st::defaultSubsectionTitle), st::defaultSubsectionTitle),
st::createPollFieldTitlePadding); st::createPollFieldTitlePadding);
const auto options = lifetime().make_state<Options>( const auto options = lifetime().make_state<Options>(
getDelegate()->outerContainer(), this,
container, container,
&_controller->session(), _controller,
_emojiPanel ? _emojiPanel.get() : nullptr,
(_chosen & PollData::Flag::Quiz)); (_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);

View file

@ -14,6 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
struct PollData; struct PollData;
namespace ChatHelpers {
class TabbedPanel;
} // namespace ChatHelpers
namespace Ui { namespace Ui {
class VerticalLayout; class VerticalLayout;
} // namespace Ui } // namespace Ui
@ -72,6 +76,7 @@ private:
const PollData::Flags _disabled = PollData::Flags(); const PollData::Flags _disabled = PollData::Flags();
const Api::SendType _sendType = Api::SendType(); const Api::SendType _sendType = Api::SendType();
const SendMenu::Type _sendMenuType; const SendMenu::Type _sendMenuType;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
Fn<void()> _setInnerFocus; Fn<void()> _setInnerFocus;
Fn<rpl::producer<bool>()> _dataIsValidValue; Fn<rpl::producer<bool>()> _dataIsValidValue;
rpl::event_stream<Result> _submitRequests; rpl::event_stream<Result> _submitRequests;