mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2026-07-25 06:54:43 +00:00
[poll-view] Added initial inline add-option overlay with input field.
This commit is contained in:
@@ -949,6 +949,8 @@ PRIVATE
|
||||
history/view/history_view_cursor_state.h
|
||||
history/view/history_view_draw_to_reply.cpp
|
||||
history/view/history_view_draw_to_reply.h
|
||||
history/view/history_view_add_poll_option.cpp
|
||||
history/view/history_view_add_poll_option.h
|
||||
history/view/history_view_element.cpp
|
||||
history/view/history_view_element.h
|
||||
history/view/history_view_emoji_interactions.cpp
|
||||
|
||||
@@ -6913,6 +6913,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_polls_votes_collapse" = "Collapse";
|
||||
"lng_polls_vote_yesterday" = "yesterday";
|
||||
"lng_polls_option_added_by" = "Added by {user}";
|
||||
"lng_polls_add_option" = "Add an Option";
|
||||
"lng_polls_add_option_placeholder" = "Option text...";
|
||||
"lng_polls_max_options_reached" = "Maximum number of options reached.";
|
||||
"lng_polls_add_option_duplicate" = "This option already exists.";
|
||||
"lng_polls_add_option_closed" = "This poll has been closed.";
|
||||
"lng_polls_add_option_error" = "Could not add the option. Please try again.";
|
||||
"lng_polls_add_option_save" = "Save";
|
||||
|
||||
"lng_todo_title" = "Checklist";
|
||||
"lng_todo_title_group" = "Group Checklist";
|
||||
|
||||
@@ -185,6 +185,47 @@ void Polls::sendVotes(
|
||||
_pollVotesRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::addAnswer(
|
||||
FullMsgId itemId,
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail) {
|
||||
if (_pollAddAnswerRequestIds.contains(itemId)) {
|
||||
return;
|
||||
}
|
||||
const auto item = _session->data().message(itemId);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
const auto sentEntities = Api::EntitiesToMTP(
|
||||
_session,
|
||||
text.entities,
|
||||
Api::ConvertOption::SkipLocal);
|
||||
const auto answer = MTP_inputPollAnswer(
|
||||
MTP_flags(MTPDinputPollAnswer::Flags(0)),
|
||||
MTP_textWithEntities(
|
||||
MTP_string(text.text),
|
||||
sentEntities),
|
||||
MTPInputMedia());
|
||||
const auto requestId = _api.request(MTPmessages_AddPollAnswer(
|
||||
item->history()->peer->input(),
|
||||
MTP_int(item->id),
|
||||
answer
|
||||
)).done([=](const MTPUpdates &result) {
|
||||
_pollAddAnswerRequestIds.erase(itemId);
|
||||
_session->updates().applyUpdates(result);
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_pollAddAnswerRequestIds.erase(itemId);
|
||||
if (fail) {
|
||||
fail(error.type());
|
||||
}
|
||||
}).send();
|
||||
_pollAddAnswerRequestIds.emplace(itemId, requestId);
|
||||
}
|
||||
|
||||
void Polls::close(not_null<HistoryItem*> item) {
|
||||
const auto itemId = item->fullId();
|
||||
if (_pollCloseRequestIds.contains(itemId)) {
|
||||
|
||||
@@ -35,6 +35,11 @@ public:
|
||||
void sendVotes(
|
||||
FullMsgId itemId,
|
||||
const std::vector<QByteArray> &options);
|
||||
void addAnswer(
|
||||
FullMsgId itemId,
|
||||
const TextWithEntities &text,
|
||||
Fn<void()> done,
|
||||
Fn<void(QString)> fail);
|
||||
void close(not_null<HistoryItem*> item);
|
||||
void reloadResults(not_null<HistoryItem*> item);
|
||||
|
||||
@@ -43,6 +48,7 @@ private:
|
||||
MTP::Sender _api;
|
||||
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollAddAnswerRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
|
||||
base::flat_map<FullMsgId, mtpRequestId> _pollReloadRequestIds;
|
||||
|
||||
|
||||
@@ -86,8 +86,29 @@ historyPollOutChosenSelected: icon {{ "poll_select_check", historyFileOutIconFgS
|
||||
historyPollInChosen: icon {{ "poll_select_check", historyFileInIconFg }};
|
||||
historyPollInChosenSelected: icon {{ "poll_select_check", historyFileInIconFgSelected }};
|
||||
|
||||
historyPollAddOptionTop: 3px;
|
||||
historyPollAddOptionHeight: 32px;
|
||||
historyPollAddOptionField: InputField(defaultInputField) {
|
||||
textBg: transparent;
|
||||
textFg: windowFg;
|
||||
placeholderFg: placeholderFg;
|
||||
placeholderFgActive: placeholderFgActive;
|
||||
placeholderFgError: placeholderFgActive;
|
||||
textMargins: margins(8px, 7px, 28px, 4px);
|
||||
heightMin: 32px;
|
||||
heightMax: 32px;
|
||||
style: TextStyle(defaultTextStyle) {
|
||||
font: font(13px);
|
||||
}
|
||||
placeholderMargins: margins(0px, 1px, 0px, 0px);
|
||||
placeholderFont: font(12px);
|
||||
placeholderScale: 0.;
|
||||
border: 0px;
|
||||
borderActive: 0px;
|
||||
}
|
||||
|
||||
pollBoxOutlinePollEmojiIcon: icon{{ "poll/general/outline_poll_emoji", activeButtonFg }};
|
||||
pollBoxOutlinePollAddIcon: icon{{ "poll/general/outline_poll_add", activeButtonFg }};
|
||||
pollBoxOutlinePollAddIcon: icon{{ "poll/general/outline_poll_add-18x18", activeButtonFg }};
|
||||
pollBoxOutlinePollAttachIcon: icon{{ "poll/general/outline_poll_attach", activeButtonFg }};
|
||||
pollBoxMenuPollOrderIcon: icon{{ "poll/general/menu_poll_order-24x24", historyComposeIconFg }};
|
||||
pollBoxFilledPollDeadlineIcon: icon{{ "poll/filled/filled_poll_deadline", activeButtonFg }};
|
||||
|
||||
@@ -697,6 +697,16 @@ void InnerWidget::elementShowPollResults(
|
||||
FullMsgId context) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementShowAddPollOption(
|
||||
not_null<HistoryView::Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementSubmitAddPollOption(FullMsgId context) {
|
||||
}
|
||||
|
||||
void InnerWidget::elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) {
|
||||
|
||||
@@ -109,6 +109,12 @@ public:
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) override;
|
||||
void elementShowAddPollOption(
|
||||
not_null<HistoryView::Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) override;
|
||||
void elementSubmitAddPollOption(FullMsgId context) override;
|
||||
void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) override;
|
||||
|
||||
@@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_context_menu.h"
|
||||
#include "history/view/history_view_reaction_preview.h"
|
||||
#include "history/view/history_view_quick_action.h"
|
||||
#include "history/view/history_view_add_poll_option.h"
|
||||
#include "history/view/history_view_emoji_interactions.h"
|
||||
#include "history/view/history_view_top_peers_selector.h"
|
||||
#include "history/history_item_components.h"
|
||||
@@ -198,6 +199,20 @@ public:
|
||||
_widget->elementShowPollResults(poll, context);
|
||||
}
|
||||
}
|
||||
void elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) override {
|
||||
if (_widget) {
|
||||
_widget->elementShowAddPollOption(view, poll, context, optionRect);
|
||||
}
|
||||
}
|
||||
void elementSubmitAddPollOption(FullMsgId context) override {
|
||||
if (_widget) {
|
||||
_widget->elementSubmitAddPollOption(context);
|
||||
}
|
||||
}
|
||||
void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) override {
|
||||
@@ -1870,6 +1885,15 @@ void HistoryInner::mousePressEvent(QMouseEvent *e) {
|
||||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
if (_addPollOptionWidget && _addPollOptionView) {
|
||||
const auto top = itemTop(_addPollOptionView);
|
||||
const auto viewRect = (top >= 0)
|
||||
? QRect(0, top, width(), _addPollOptionView->height())
|
||||
: QRect();
|
||||
if (!viewRect.contains(e->pos())) {
|
||||
hideAddPollOption();
|
||||
}
|
||||
}
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
_middleClickAutoscroll.stop();
|
||||
e->accept();
|
||||
@@ -2180,6 +2204,11 @@ void HistoryInner::itemRemoved(not_null<const HistoryItem*> item) {
|
||||
}
|
||||
|
||||
void HistoryInner::viewRemoved(not_null<const Element*> view) {
|
||||
if (_addPollOptionView == view) {
|
||||
_addPollOptionWidget = nullptr;
|
||||
_addPollOptionView = nullptr;
|
||||
_addPollOptionContext = FullMsgId();
|
||||
}
|
||||
const auto refresh = [&](auto &saved) {
|
||||
if (saved == view) {
|
||||
const auto now = viewByItem(view->data());
|
||||
@@ -3944,6 +3973,8 @@ void HistoryInner::visibleAreaUpdated(int top, int bottom) {
|
||||
_emojiInteractions->visibleAreaUpdated(
|
||||
_visibleAreaTop,
|
||||
_visibleAreaBottom);
|
||||
|
||||
updateAddPollOptionPosition();
|
||||
}
|
||||
|
||||
bool HistoryInner::displayScrollDate() const {
|
||||
@@ -4101,6 +4132,7 @@ void HistoryInner::leaveEventHook(QEvent *e) {
|
||||
}
|
||||
|
||||
HistoryInner::~HistoryInner() {
|
||||
hideAddPollOption();
|
||||
_aboutView = nullptr;
|
||||
for (const auto &item : _animatedStickersPlayed) {
|
||||
if (const auto view = item->mainView()) {
|
||||
@@ -4272,6 +4304,98 @@ void HistoryInner::elementShowPollResults(
|
||||
_controller->showPollResults(poll, context);
|
||||
}
|
||||
|
||||
void HistoryInner::elementShowAddPollOption(
|
||||
not_null<HistoryView::Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) {
|
||||
showAddPollOption(view, poll, context);
|
||||
}
|
||||
|
||||
void HistoryInner::elementSubmitAddPollOption(FullMsgId context) {
|
||||
if (_addPollOptionWidget && _addPollOptionContext == context) {
|
||||
_addPollOptionWidget->triggerSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryInner::showAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) {
|
||||
if (_addPollOptionWidget && _addPollOptionContext == context) {
|
||||
hideAddPollOption();
|
||||
return;
|
||||
}
|
||||
hideAddPollOption();
|
||||
|
||||
_addPollOptionView = view;
|
||||
_addPollOptionContext = context;
|
||||
|
||||
if (const auto media = view->media()) {
|
||||
media->setAddOptionActive(true);
|
||||
}
|
||||
|
||||
_addPollOptionWidget = base::make_unique_q<
|
||||
HistoryView::AddPollOptionWidget>(
|
||||
this,
|
||||
poll,
|
||||
context,
|
||||
&poll->session());
|
||||
|
||||
_addPollOptionWidget->submitted(
|
||||
) | rpl::on_next([=] {
|
||||
hideAddPollOption();
|
||||
}, _addPollOptionWidget->lifetime());
|
||||
|
||||
_addPollOptionWidget->cancelled(
|
||||
) | rpl::on_next([=] {
|
||||
hideAddPollOption();
|
||||
}, _addPollOptionWidget->lifetime());
|
||||
|
||||
updateAddPollOptionPosition();
|
||||
_addPollOptionWidget->show();
|
||||
}
|
||||
|
||||
void HistoryInner::hideAddPollOption() {
|
||||
if (!_addPollOptionWidget) {
|
||||
return;
|
||||
}
|
||||
if (_addPollOptionView) {
|
||||
if (const auto media = _addPollOptionView->media()) {
|
||||
media->setAddOptionActive(false);
|
||||
}
|
||||
}
|
||||
_addPollOptionWidget = nullptr;
|
||||
_addPollOptionView = nullptr;
|
||||
_addPollOptionContext = FullMsgId();
|
||||
}
|
||||
|
||||
void HistoryInner::updateAddPollOptionPosition() {
|
||||
if (!_addPollOptionWidget || !_addPollOptionView) {
|
||||
return;
|
||||
}
|
||||
const auto view = _addPollOptionView;
|
||||
const auto top = itemTop(view);
|
||||
if (top < 0) {
|
||||
return;
|
||||
}
|
||||
const auto media = view->media();
|
||||
if (!media) {
|
||||
hideAddPollOption();
|
||||
return;
|
||||
}
|
||||
const auto mediaPos = view->mediaTopLeft();
|
||||
const auto innerWidth = view->innerGeometry().width()
|
||||
- st::msgPadding.left()
|
||||
- st::msgPadding.right();
|
||||
const auto rect = media->addOptionRect(innerWidth);
|
||||
_addPollOptionWidget->updatePosition(
|
||||
QPoint(
|
||||
mediaPos.x() + rect.x(),
|
||||
top + mediaPos.y() + rect.y()),
|
||||
rect.width());
|
||||
}
|
||||
|
||||
void HistoryInner::elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) {
|
||||
|
||||
@@ -37,6 +37,7 @@ struct StateRequest;
|
||||
enum class CursorState : char;
|
||||
enum class PointState : char;
|
||||
enum class ElementChatMode : char;
|
||||
class AddPollOptionWidget;
|
||||
class EmptyPainter;
|
||||
class Element;
|
||||
class TranslateTracker;
|
||||
@@ -155,6 +156,12 @@ public:
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
void elementShowAddPollOption(
|
||||
not_null<HistoryView::Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect);
|
||||
void elementSubmitAddPollOption(FullMsgId context);
|
||||
void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context);
|
||||
@@ -255,6 +262,13 @@ protected:
|
||||
void contextMenuEvent(QContextMenuEvent *e) override;
|
||||
|
||||
private:
|
||||
void showAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
void hideAddPollOption();
|
||||
void updateAddPollOptionPosition();
|
||||
|
||||
void onTouchSelect();
|
||||
void onTouchScrollTimer();
|
||||
void markReadMetricsStale();
|
||||
@@ -596,6 +610,10 @@ private:
|
||||
ClickHandlerPtr _scrollDateLink;
|
||||
ClickHandlerPtr _forumThreadBarLink;
|
||||
|
||||
base::unique_qptr<HistoryView::AddPollOptionWidget> _addPollOptionWidget;
|
||||
Element *_addPollOptionView = nullptr;
|
||||
FullMsgId _addPollOptionContext;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool CanSendReply(not_null<const HistoryItem*> item);
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 "history/view/history_view_add_poll_option.h"
|
||||
|
||||
#include "api/api_polls.h"
|
||||
#include "apiwrap.h"
|
||||
#include "base/event_filter.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "data/data_session.h"
|
||||
#include "history/history_item.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "ui/widgets/fields/input_field.h"
|
||||
#include "ui/toast/toast.h"
|
||||
#include "styles/style_chat.h"
|
||||
#include "styles/style_polls.h"
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
AddPollOptionWidget::AddPollOptionWidget(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId itemId,
|
||||
not_null<Main::Session*> session)
|
||||
: RpWidget(parent)
|
||||
, _poll(poll)
|
||||
, _itemId(itemId)
|
||||
, _session(session) {
|
||||
setupField();
|
||||
subscribeToPollUpdates();
|
||||
}
|
||||
|
||||
void AddPollOptionWidget::setupField() {
|
||||
_field = Ui::CreateChild<Ui::InputField>(
|
||||
this,
|
||||
st::historyPollAddOptionField,
|
||||
Ui::InputField::Mode::SingleLine,
|
||||
tr::lng_polls_add_option_placeholder());
|
||||
|
||||
_field->setMaxLength(100);
|
||||
|
||||
const auto field = _field;
|
||||
sizeValue(
|
||||
) | rpl::on_next([field](QSize size) {
|
||||
field->setGeometry(0, 0, size.width(), size.height());
|
||||
}, _field->lifetime());
|
||||
|
||||
_field->submits(
|
||||
) | rpl::on_next([=] {
|
||||
triggerSubmit();
|
||||
}, _field->lifetime());
|
||||
|
||||
base::install_event_filter(_field, [=](not_null<QEvent*> event) {
|
||||
if (event->type() == QEvent::KeyPress) {
|
||||
if (static_cast<QKeyEvent*>(event.get())->key() == Qt::Key_Escape) {
|
||||
_cancelledEvents.fire({});
|
||||
return base::EventFilterResult::Cancel;
|
||||
}
|
||||
}
|
||||
return base::EventFilterResult::Continue;
|
||||
});
|
||||
|
||||
_field->setFocusFast();
|
||||
}
|
||||
|
||||
void AddPollOptionWidget::subscribeToPollUpdates() {
|
||||
_session->data().pollUpdates(
|
||||
) | rpl::filter([=](not_null<PollData*> poll) {
|
||||
return (poll == _poll);
|
||||
}) | rpl::on_next([=](not_null<PollData*> poll) {
|
||||
if (poll->closed()
|
||||
|| (int(poll->answers.size())
|
||||
>= _session->appConfig().pollOptionsLimit())) {
|
||||
_cancelledEvents.fire({});
|
||||
}
|
||||
}, lifetime());
|
||||
|
||||
_session->data().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> item) {
|
||||
return (item->fullId() == _itemId);
|
||||
}) | rpl::on_next([=] {
|
||||
_cancelledEvents.fire({});
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void AddPollOptionWidget::triggerSubmit() {
|
||||
const auto text = _field->getLastText().trimmed();
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (int(_poll->answers.size())
|
||||
>= _session->appConfig().pollOptionsLimit()) {
|
||||
Ui::Toast::Show(
|
||||
parentWidget(),
|
||||
tr::lng_polls_max_options_reached(tr::now));
|
||||
return;
|
||||
}
|
||||
|
||||
_field->setEnabled(false);
|
||||
|
||||
_session->api().polls().addAnswer(
|
||||
_itemId,
|
||||
{ text },
|
||||
[=] { _submittedEvents.fire({}); },
|
||||
[=](QString error) {
|
||||
_field->setEnabled(true);
|
||||
Ui::Toast::Show(
|
||||
parentWidget(),
|
||||
mapErrorToText(error));
|
||||
});
|
||||
}
|
||||
|
||||
QString AddPollOptionWidget::mapErrorToText(const QString &error) {
|
||||
if (error == u"POLL_ANSWERS_TOO_MUCH"_q) {
|
||||
return tr::lng_polls_max_options_reached(tr::now);
|
||||
} else if (error == u"POLL_ANSWER_DUPLICATE"_q) {
|
||||
return tr::lng_polls_add_option_duplicate(tr::now);
|
||||
} else if (error.startsWith(u"POLL"_q) && error.contains(u"CLOSE"_q)) {
|
||||
return tr::lng_polls_add_option_closed(tr::now);
|
||||
}
|
||||
return tr::lng_polls_add_option_error(tr::now);
|
||||
}
|
||||
|
||||
void AddPollOptionWidget::updatePosition(QPoint topLeft, int w) {
|
||||
setGeometry(
|
||||
topLeft.x(),
|
||||
topLeft.y() + st::historyPollAddOptionTop,
|
||||
w,
|
||||
st::historyPollAddOptionField.heightMin);
|
||||
}
|
||||
|
||||
rpl::producer<> AddPollOptionWidget::submitted() const {
|
||||
return _submittedEvents.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AddPollOptionWidget::cancelled() const {
|
||||
return _cancelledEvents.events();
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
struct PollData;
|
||||
|
||||
namespace Ui {
|
||||
class InputField;
|
||||
} // namespace Ui
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class AddPollOptionWidget final : public Ui::RpWidget {
|
||||
public:
|
||||
AddPollOptionWidget(
|
||||
not_null<QWidget*> parent,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId itemId,
|
||||
not_null<Main::Session*> session);
|
||||
|
||||
void updatePosition(QPoint topLeft, int width);
|
||||
void triggerSubmit();
|
||||
|
||||
[[nodiscard]] rpl::producer<> submitted() const;
|
||||
[[nodiscard]] rpl::producer<> cancelled() const;
|
||||
|
||||
private:
|
||||
void setupField();
|
||||
void subscribeToPollUpdates();
|
||||
[[nodiscard]] static QString mapErrorToText(const QString &error);
|
||||
|
||||
const not_null<PollData*> _poll;
|
||||
const FullMsgId _itemId;
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
Ui::InputField *_field = nullptr;
|
||||
|
||||
rpl::event_stream<> _submittedEvents;
|
||||
rpl::event_stream<> _cancelledEvents;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
||||
@@ -505,6 +505,16 @@ void DefaultElementDelegate::elementShowPollResults(
|
||||
FullMsgId context) {
|
||||
}
|
||||
|
||||
void DefaultElementDelegate::elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) {
|
||||
}
|
||||
|
||||
void DefaultElementDelegate::elementSubmitAddPollOption(FullMsgId context) {
|
||||
}
|
||||
|
||||
void DefaultElementDelegate::elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) {
|
||||
@@ -2884,6 +2894,10 @@ QRect Element::effectIconGeometry() const {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
QPoint Element::mediaTopLeft() const {
|
||||
return innerGeometry().topLeft();
|
||||
}
|
||||
|
||||
Element::~Element() {
|
||||
setReactions(nullptr);
|
||||
|
||||
|
||||
@@ -107,6 +107,12 @@ public:
|
||||
virtual void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) = 0;
|
||||
virtual void elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) = 0;
|
||||
virtual void elementSubmitAddPollOption(FullMsgId context) = 0;
|
||||
virtual void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) = 0;
|
||||
@@ -163,6 +169,12 @@ public:
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) override;
|
||||
void elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) override;
|
||||
void elementSubmitAddPollOption(FullMsgId context) override;
|
||||
void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) override;
|
||||
@@ -637,6 +649,7 @@ public:
|
||||
|
||||
[[nodiscard]] virtual QRect effectIconGeometry() const;
|
||||
[[nodiscard]] virtual QRect innerGeometry() const = 0;
|
||||
[[nodiscard]] virtual QPoint mediaTopLeft() const;
|
||||
|
||||
void customEmojiRepaint();
|
||||
void prepareCustomEmojiPaint(
|
||||
|
||||
@@ -29,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/view/history_view_translate_tracker.h"
|
||||
#include "history/view/history_view_read_metrics_tracker.h"
|
||||
#include "history/view/history_view_add_poll_option.h"
|
||||
#include "data/data_poll.h"
|
||||
#include "history/view/history_view_top_peers_selector.h"
|
||||
#include "history/view/history_view_quick_action.h"
|
||||
#include "chat_helpers/message_field.h"
|
||||
@@ -1174,6 +1176,7 @@ void ListWidget::visibleTopBottomUpdated(
|
||||
_applyUpdatedScrollState.call();
|
||||
|
||||
_emojiInteractions->visibleAreaUpdated(_visibleTop, _visibleBottom);
|
||||
updateAddPollOptionPosition();
|
||||
}
|
||||
|
||||
void ListWidget::applyUpdatedScrollState() {
|
||||
@@ -1889,6 +1892,98 @@ void ListWidget::elementShowPollResults(
|
||||
_delegate->listShowPollResults(poll, context);
|
||||
}
|
||||
|
||||
void ListWidget::elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) {
|
||||
showAddPollOption(view, poll, context);
|
||||
}
|
||||
|
||||
void ListWidget::elementSubmitAddPollOption(FullMsgId context) {
|
||||
if (_addPollOptionWidget && _addPollOptionContext == context) {
|
||||
_addPollOptionWidget->triggerSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
void ListWidget::showAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) {
|
||||
if (_addPollOptionWidget && _addPollOptionContext == context) {
|
||||
hideAddPollOption();
|
||||
return;
|
||||
}
|
||||
hideAddPollOption();
|
||||
|
||||
_addPollOptionView = view;
|
||||
_addPollOptionContext = context;
|
||||
|
||||
if (const auto media = view->media()) {
|
||||
media->setAddOptionActive(true);
|
||||
}
|
||||
|
||||
_addPollOptionWidget = base::make_unique_q<AddPollOptionWidget>(
|
||||
this,
|
||||
poll,
|
||||
context,
|
||||
&poll->session());
|
||||
|
||||
_addPollOptionWidget->submitted(
|
||||
) | rpl::on_next([=] {
|
||||
hideAddPollOption();
|
||||
}, _addPollOptionWidget->lifetime());
|
||||
|
||||
_addPollOptionWidget->cancelled(
|
||||
) | rpl::on_next([=] {
|
||||
hideAddPollOption();
|
||||
}, _addPollOptionWidget->lifetime());
|
||||
|
||||
updateAddPollOptionPosition();
|
||||
_addPollOptionWidget->show();
|
||||
}
|
||||
|
||||
void ListWidget::hideAddPollOption() {
|
||||
if (!_addPollOptionWidget) {
|
||||
return;
|
||||
}
|
||||
if (_addPollOptionView) {
|
||||
if (const auto media = _addPollOptionView->media()) {
|
||||
media->setAddOptionActive(false);
|
||||
}
|
||||
}
|
||||
_addPollOptionWidget = nullptr;
|
||||
_addPollOptionView = nullptr;
|
||||
_addPollOptionContext = FullMsgId();
|
||||
_delegate->listWindowSetInnerFocus();
|
||||
}
|
||||
|
||||
void ListWidget::updateAddPollOptionPosition() {
|
||||
if (!_addPollOptionWidget || !_addPollOptionView) {
|
||||
return;
|
||||
}
|
||||
const auto view = _addPollOptionView;
|
||||
const auto top = itemTop(view);
|
||||
if (top < 0) {
|
||||
return;
|
||||
}
|
||||
const auto media = view->media();
|
||||
if (!media) {
|
||||
hideAddPollOption();
|
||||
return;
|
||||
}
|
||||
const auto mediaPos = view->mediaTopLeft();
|
||||
const auto innerWidth = view->innerGeometry().width()
|
||||
- st::msgPadding.left()
|
||||
- st::msgPadding.right();
|
||||
const auto rect = media->addOptionRect(innerWidth);
|
||||
_addPollOptionWidget->updatePosition(
|
||||
QPoint(
|
||||
mediaPos.x() + rect.x(),
|
||||
top + mediaPos.y() + rect.y()),
|
||||
rect.width());
|
||||
}
|
||||
|
||||
void ListWidget::elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) {
|
||||
@@ -3069,6 +3164,15 @@ void ListWidget::mousePressEvent(QMouseEvent *e) {
|
||||
e->accept();
|
||||
return; // ignore mouse press, that was hiding context menu
|
||||
}
|
||||
if (_addPollOptionWidget && _addPollOptionView) {
|
||||
const auto top = itemTop(_addPollOptionView);
|
||||
const auto viewRect = (top >= 0)
|
||||
? QRect(0, top, width(), _addPollOptionView->height())
|
||||
: QRect();
|
||||
if (!viewRect.contains(e->pos())) {
|
||||
hideAddPollOption();
|
||||
}
|
||||
}
|
||||
if (_middleClickAutoscroll.active()) {
|
||||
_middleClickAutoscroll.stop();
|
||||
e->accept();
|
||||
@@ -4357,6 +4461,9 @@ void ListWidget::showItemHighlight(not_null<HistoryItem*> item) {
|
||||
}
|
||||
|
||||
void ListWidget::viewReplaced(not_null<const Element*> was, Element *now) {
|
||||
if (_addPollOptionView == was) {
|
||||
hideAddPollOption();
|
||||
}
|
||||
if (_visibleTopItem == was) _visibleTopItem = now;
|
||||
if (_scrollDateLastItem == was) _scrollDateLastItem = now;
|
||||
if (_overElement == was) _overElement = now;
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace HistoryView {
|
||||
|
||||
struct TextState;
|
||||
struct StateRequest;
|
||||
class AddPollOptionWidget;
|
||||
class EmojiInteractions;
|
||||
class TranslateTracker;
|
||||
class ReadMetricsTracker;
|
||||
@@ -416,6 +417,12 @@ public:
|
||||
void elementShowPollResults(
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context) override;
|
||||
void elementShowAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context,
|
||||
QRect optionRect) override;
|
||||
void elementSubmitAddPollOption(FullMsgId context) override;
|
||||
void elementOpenPhoto(
|
||||
not_null<PhotoData*> photo,
|
||||
FullMsgId context) override;
|
||||
@@ -540,6 +547,13 @@ private:
|
||||
Deselecting,
|
||||
};
|
||||
|
||||
void showAddPollOption(
|
||||
not_null<Element*> view,
|
||||
not_null<PollData*> poll,
|
||||
FullMsgId context);
|
||||
void hideAddPollOption();
|
||||
void updateAddPollOptionPosition();
|
||||
|
||||
void onTouchSelect();
|
||||
void onTouchScrollTimer();
|
||||
void markReadMetricsStale();
|
||||
@@ -893,6 +907,10 @@ private:
|
||||
rpl::event_stream<FullMsgId> _requestedToShowMessage;
|
||||
rpl::event_stream<not_null<QKeyEvent*>> _scrollKeyEvents;
|
||||
|
||||
base::unique_qptr<AddPollOptionWidget> _addPollOptionWidget;
|
||||
Element *_addPollOptionView = nullptr;
|
||||
FullMsgId _addPollOptionContext;
|
||||
|
||||
rpl::lifetime _viewerLifetime;
|
||||
|
||||
};
|
||||
|
||||
@@ -1233,6 +1233,7 @@ void Message::draw(Painter &p, const PaintContext &context) const {
|
||||
const auto maybeMediaHighlight = context.highlightPathCache
|
||||
&& context.highlightPathCache->isEmpty();
|
||||
auto mediaPosition = QPoint(inner.left(), top);
|
||||
_lastMediaPosition = mediaPosition;
|
||||
p.translate(mediaPosition);
|
||||
media->draw(p, context.translated(
|
||||
-mediaPosition
|
||||
@@ -4815,6 +4816,10 @@ QRect Message::innerGeometry() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
QPoint Message::mediaTopLeft() const {
|
||||
return _lastMediaPosition;
|
||||
}
|
||||
|
||||
bool Message::isCommentsRootView() const {
|
||||
return context() == Context::Replies
|
||||
&& data()->isDiscussionPost()
|
||||
|
||||
@@ -189,6 +189,7 @@ public:
|
||||
|
||||
QRect effectIconGeometry() const override;
|
||||
QRect innerGeometry() const override;
|
||||
QPoint mediaTopLeft() const override;
|
||||
[[nodiscard]] BottomRippleMask bottomRippleMask(int buttonHeight) const;
|
||||
|
||||
private:
|
||||
@@ -398,6 +399,7 @@ private:
|
||||
uint32 _postShowingAuthor : 1 = 0;
|
||||
|
||||
BottomInfo _bottomInfo;
|
||||
mutable QPoint _lastMediaPosition;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -184,6 +184,12 @@ public:
|
||||
virtual void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual QRect addOptionRect(int innerWidth) const {
|
||||
return {};
|
||||
}
|
||||
virtual void setAddOptionActive(bool active) {
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual bool uploading() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/crc32hash.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/timer.h"
|
||||
#include "main/main_app_config.h"
|
||||
#include "main/main_session.h"
|
||||
#include "apiwrap.h"
|
||||
#include "api/api_polls.h"
|
||||
@@ -470,6 +471,36 @@ Poll::Poll(
|
||||
updateAnswerVotes();
|
||||
startAnswersAnimation();
|
||||
})))
|
||||
, _addOptionLink(
|
||||
std::make_shared<LambdaClickHandler>(crl::guard(
|
||||
this,
|
||||
[=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto delegate = my.elementDelegate
|
||||
? my.elementDelegate()
|
||||
: nullptr) {
|
||||
const auto innerWidth = width()
|
||||
- st::msgPadding.left()
|
||||
- st::msgPadding.right();
|
||||
delegate->elementShowAddPollOption(
|
||||
_parent,
|
||||
_poll,
|
||||
_parent->data()->fullId(),
|
||||
addOptionRect(innerWidth));
|
||||
}
|
||||
})))
|
||||
, _saveOptionLink(
|
||||
std::make_shared<LambdaClickHandler>(crl::guard(
|
||||
this,
|
||||
[=](ClickContext context) {
|
||||
const auto my = context.other.value<ClickHandlerContext>();
|
||||
if (const auto delegate = my.elementDelegate
|
||||
? my.elementDelegate()
|
||||
: nullptr) {
|
||||
delegate->elementSubmitAddPollOption(
|
||||
_parent->data()->fullId());
|
||||
}
|
||||
})))
|
||||
, _closeTimer([=] { repaint(); }) {
|
||||
if (!consumed.text.isEmpty()) {
|
||||
updateDescription();
|
||||
@@ -510,6 +541,7 @@ QSize Poll::countOptimalSize() {
|
||||
+ padding.bottom();
|
||||
}), 0);
|
||||
|
||||
const auto addOptionHeight = canAddOption() ? this->addOptionHeight() : 0;
|
||||
const auto bottomButtonHeight = inlineFooter()
|
||||
? 0
|
||||
: st::historyPollBottomButtonSkip;
|
||||
@@ -521,6 +553,7 @@ QSize Poll::countOptimalSize() {
|
||||
+ st::msgDateFont->height
|
||||
+ st::historyPollAnswersSkip
|
||||
+ answersHeight
|
||||
+ addOptionHeight
|
||||
+ st::historyPollTotalVotesSkip
|
||||
+ bottomButtonHeight
|
||||
+ st::msgDateFont->height
|
||||
@@ -564,6 +597,45 @@ bool Poll::inlineFooter() const {
|
||||
& (PollData::Flag::PublicVotes | PollData::Flag::MultiChoice));
|
||||
}
|
||||
|
||||
bool Poll::canAddOption() const {
|
||||
return (_flags & PollData::Flag::OpenAnswers)
|
||||
&& !(_flags & PollData::Flag::Closed)
|
||||
&& (int(_poll->answers.size())
|
||||
< _poll->session().appConfig().pollOptionsLimit());
|
||||
}
|
||||
|
||||
int Poll::addOptionHeight() const {
|
||||
return st::historyPollAnswerPaddingNoMedia.top()
|
||||
+ st::msgDateFont->height
|
||||
+ st::historyPollAnswerPaddingNoMedia.bottom();
|
||||
}
|
||||
|
||||
QRect Poll::addOptionRect(int innerWidth) const {
|
||||
const auto answersHeight = ranges::accumulate(ranges::views::all(
|
||||
_answers
|
||||
) | ranges::views::transform([&](const Answer &answer) {
|
||||
return countAnswerHeight(answer, innerWidth);
|
||||
}), 0);
|
||||
const auto top = countQuestionTop(innerWidth)
|
||||
+ _question.countHeight(innerWidth)
|
||||
+ st::historyPollSubtitleSkip
|
||||
+ st::msgDateFont->height
|
||||
+ st::historyPollAnswersSkip
|
||||
+ answersHeight;
|
||||
return QRect(
|
||||
st::msgPadding.left(),
|
||||
top,
|
||||
innerWidth,
|
||||
addOptionHeight());
|
||||
}
|
||||
|
||||
void Poll::setAddOptionActive(bool active) {
|
||||
if (_addOptionActive != active) {
|
||||
_addOptionActive = active;
|
||||
history()->owner().requestViewResize(_parent);
|
||||
}
|
||||
}
|
||||
|
||||
int Poll::countAnswerTop(
|
||||
const Answer &answer,
|
||||
int innerWidth) const {
|
||||
@@ -624,6 +696,7 @@ QSize Poll::countCurrentSize(int newWidth) {
|
||||
return countAnswerHeight(answer, innerWidth);
|
||||
}), 0);
|
||||
|
||||
const auto addOptionHeight = canAddOption() ? this->addOptionHeight() : 0;
|
||||
const auto bottomButtonHeight = inlineFooter()
|
||||
? 0
|
||||
: st::historyPollBottomButtonSkip;
|
||||
@@ -634,6 +707,7 @@ QSize Poll::countCurrentSize(int newWidth) {
|
||||
+ st::msgDateFont->height
|
||||
+ st::historyPollAnswersSkip
|
||||
+ answersHeight
|
||||
+ addOptionHeight
|
||||
+ st::historyPollTotalVotesSkip
|
||||
+ bottomButtonHeight
|
||||
+ st::msgDateFont->height
|
||||
@@ -1636,6 +1710,10 @@ void Poll::draw(Painter &p, const PaintContext &context) const {
|
||||
context);
|
||||
tshift += height;
|
||||
}
|
||||
if (canAddOption()) {
|
||||
paintAddOption(p, padding.left(), tshift, paintw, context);
|
||||
tshift += addOptionHeight();
|
||||
}
|
||||
if (!inlineFooter()) {
|
||||
paintBottom(p, padding.left(), tshift, paintw, context);
|
||||
} else if (!_totalVotesLabel.isEmpty()) {
|
||||
@@ -1693,6 +1771,55 @@ void Poll::paintInlineFooter(
|
||||
}
|
||||
}
|
||||
|
||||
void Poll::paintAddOption(
|
||||
Painter &p,
|
||||
int left,
|
||||
int top,
|
||||
int paintw,
|
||||
const PaintContext &context) const {
|
||||
if (_addOptionActive) {
|
||||
return;
|
||||
}
|
||||
const auto stm = context.messageStyle();
|
||||
const auto height = addOptionHeight();
|
||||
const auto &padding = st::historyPollAnswerPaddingNoMedia;
|
||||
const auto textTop = top + padding.top();
|
||||
|
||||
if (_addOptionRipple) {
|
||||
p.setOpacity(st::historyPollRippleOpacity);
|
||||
_addOptionRipple->paint(
|
||||
p,
|
||||
left - st::msgPadding.left(),
|
||||
top,
|
||||
width(),
|
||||
&stm->msgWaveformInactive->c);
|
||||
if (_addOptionRipple->empty()) {
|
||||
_addOptionRipple.reset();
|
||||
}
|
||||
p.setOpacity(1.);
|
||||
}
|
||||
|
||||
const auto color = stm->msgDateFg->c;
|
||||
const auto &icon = st::pollBoxOutlinePollAddIcon;
|
||||
const auto &radio = st::historyPollRadio;
|
||||
const auto iconLeft = left
|
||||
+ (radio.diameter - icon.width()) / 2;
|
||||
const auto iconTop = textTop
|
||||
+ (st::msgDateFont->height - icon.height()) / 2;
|
||||
icon.paint(p, iconLeft, iconTop, width(), color);
|
||||
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(stm->msgDateFg);
|
||||
const auto text = tr::lng_polls_add_option(tr::now);
|
||||
const auto textw = st::normalFont->width(text);
|
||||
p.drawTextLeft(
|
||||
left + st::historyPollAnswerPadding.left(),
|
||||
textTop,
|
||||
width(),
|
||||
text,
|
||||
textw);
|
||||
}
|
||||
|
||||
void Poll::paintBottom(
|
||||
Painter &p,
|
||||
int left,
|
||||
@@ -1703,6 +1830,19 @@ void Poll::paintBottom(
|
||||
+ st::msgPadding.bottom()
|
||||
+ st::historyPollBottomButtonTop;
|
||||
const auto stm = context.messageStyle();
|
||||
if (_addOptionActive) {
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(stm->msgFileThumbLinkFg);
|
||||
const auto text = tr::lng_polls_add_option_save(tr::now);
|
||||
const auto textw = st::semiboldFont->width(text);
|
||||
p.drawTextLeft(
|
||||
left + (paintw - textw) / 2,
|
||||
stringtop,
|
||||
width(),
|
||||
text,
|
||||
textw);
|
||||
return;
|
||||
}
|
||||
if (isAuthorNotVoted() && !_adminShowResults && !canSendVotes()) {
|
||||
p.setPen(stm->msgFileThumbLinkFg);
|
||||
const auto labelWidth = _adminVotesLabel.maxWidth();
|
||||
@@ -2769,10 +2909,25 @@ TextState Poll::textState(QPoint point, StateRequest request) const {
|
||||
}
|
||||
tshift += height;
|
||||
}
|
||||
if (canAddOption()) {
|
||||
const auto optHeight = addOptionHeight();
|
||||
if (QRect(0, tshift, width(), optHeight).contains(point)) {
|
||||
_lastLinkPoint = point;
|
||||
result.link = _addOptionLink;
|
||||
return result;
|
||||
}
|
||||
tshift += optHeight;
|
||||
}
|
||||
{
|
||||
const auto linkHeight = bottomButtonHeight();
|
||||
const auto linkTop = height() - linkHeight;
|
||||
if (isAuthorNotVoted() && !_adminShowResults && !canSendVotes()) {
|
||||
if (_addOptionActive) {
|
||||
if (QRect(0, linkTop, width(), linkHeight).contains(point)) {
|
||||
_lastLinkPoint = point;
|
||||
result.link = _saveOptionLink;
|
||||
return result;
|
||||
}
|
||||
} else if (isAuthorNotVoted() && !_adminShowResults && !canSendVotes()) {
|
||||
if (QRect(0, linkTop, width(), linkHeight).contains(point)) {
|
||||
_lastLinkPoint = point;
|
||||
result.link = _adminVotesLink;
|
||||
@@ -2872,10 +3027,13 @@ void Poll::clickHandlerPressedChanged(
|
||||
if (canVote()) {
|
||||
toggleRipple(*i, pressed);
|
||||
}
|
||||
} else if (handler == _addOptionLink) {
|
||||
toggleAddOptionRipple(pressed);
|
||||
} else if (handler == _sendVotesLink
|
||||
|| handler == _showResultsLink
|
||||
|| handler == _adminVotesLink
|
||||
|| handler == _adminBackVoteLink) {
|
||||
|| handler == _adminBackVoteLink
|
||||
|| handler == _saveOptionLink) {
|
||||
toggleLinkRipple(pressed);
|
||||
}
|
||||
if (_attachedMediaAttach) {
|
||||
@@ -3002,6 +3160,29 @@ bool Poll::timerFooterMultiline(int paintw) const {
|
||||
return (paintw + fullw) / 2 > paintw - skipw;
|
||||
}
|
||||
|
||||
void Poll::toggleAddOptionRipple(bool pressed) {
|
||||
if (pressed) {
|
||||
const auto optWidth = width();
|
||||
const auto optHeight = addOptionHeight();
|
||||
if (!_addOptionRipple) {
|
||||
auto mask = Ui::RippleAnimation::RectMask(
|
||||
QSize(optWidth, optHeight));
|
||||
_addOptionRipple = std::make_unique<Ui::RippleAnimation>(
|
||||
st::defaultRippleAnimation,
|
||||
std::move(mask),
|
||||
[=] { repaint(); });
|
||||
}
|
||||
const auto innerWidth = optWidth
|
||||
- st::msgPadding.left()
|
||||
- st::msgPadding.right();
|
||||
const auto rect = addOptionRect(innerWidth);
|
||||
_addOptionRipple->add(_lastLinkPoint
|
||||
- QPoint(0, rect.y()));
|
||||
} else if (_addOptionRipple) {
|
||||
_addOptionRipple->lastStop();
|
||||
}
|
||||
}
|
||||
|
||||
void Poll::toggleLinkRipple(bool pressed) {
|
||||
if (pressed) {
|
||||
const auto linkWidth = width();
|
||||
|
||||
@@ -71,6 +71,9 @@ public:
|
||||
bool hasHeavyPart() const override;
|
||||
void parentTextUpdated() override;
|
||||
|
||||
[[nodiscard]] QRect addOptionRect(int innerWidth) const override;
|
||||
void setAddOptionActive(bool active) override;
|
||||
|
||||
private:
|
||||
struct AnswerAnimation;
|
||||
struct AnswersAnimation;
|
||||
@@ -201,6 +204,7 @@ private:
|
||||
void radialAnimationCallback() const;
|
||||
|
||||
void toggleRipple(Answer &answer, bool pressed);
|
||||
void toggleAddOptionRipple(bool pressed);
|
||||
void toggleLinkRipple(bool pressed);
|
||||
void toggleMultiOption(const QByteArray &option);
|
||||
void sendMultiOptions();
|
||||
@@ -229,6 +233,15 @@ private:
|
||||
int paintw,
|
||||
const PaintContext &context) const;
|
||||
|
||||
[[nodiscard]] bool canAddOption() const;
|
||||
[[nodiscard]] int addOptionHeight() const;
|
||||
void paintAddOption(
|
||||
Painter &p,
|
||||
int left,
|
||||
int top,
|
||||
int paintw,
|
||||
const PaintContext &context) const;
|
||||
|
||||
[[nodiscard]] int bottomButtonHeight() const;
|
||||
[[nodiscard]] QString closeTimerText() const;
|
||||
[[nodiscard]] bool timerFooterMultiline(int paintw) const;
|
||||
@@ -257,7 +270,10 @@ private:
|
||||
ClickHandlerPtr _sendVotesLink;
|
||||
ClickHandlerPtr _adminVotesLink;
|
||||
ClickHandlerPtr _adminBackVoteLink;
|
||||
ClickHandlerPtr _addOptionLink;
|
||||
ClickHandlerPtr _saveOptionLink;
|
||||
mutable ClickHandlerPtr _showSolutionLink;
|
||||
mutable std::unique_ptr<Ui::RippleAnimation> _addOptionRipple;
|
||||
mutable std::unique_ptr<Ui::RippleAnimation> _linkRipple;
|
||||
mutable int _linkRippleShift = 0;
|
||||
|
||||
@@ -283,6 +299,7 @@ private:
|
||||
bool _hasSelected = false;
|
||||
bool _anyAnswerHasMedia = false;
|
||||
bool _votedFromHere = false;
|
||||
bool _addOptionActive = false;
|
||||
mutable bool _wrongAnswerAnimated = false;
|
||||
mutable bool _adminShowResults = false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user