Implement polls voting and actions.

This commit is contained in:
John Preston 2018-12-19 15:20:04 +04:00
parent 4bb5dcf50c
commit 74c1db740d
16 changed files with 423 additions and 119 deletions

View File

@ -1832,6 +1832,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_polls_anonymous" = "Anonymous Poll";
"lng_polls_votes_count#one" = "{count} vote";
"lng_polls_votes_count#other" = "{count} votes";
"lng_polls_retract" = "Retract vote";
"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";
// Wnd specific

View File

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_drafts.h"
#include "data/data_photo.h"
#include "data/data_web_page.h"
#include "data/data_poll.h"
#include "data/data_feed.h"
#include "data/data_media_types.h"
#include "data/data_sparse_ids.h"
@ -4751,35 +4752,35 @@ void ApiWrap::sendExistingDocument(
};
performRequest();
//AssertIsDebug();
//auto answers = QVector<MTPPollAnswer>{
// 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<uint64>()),
// MTP_flags(0),
// MTP_string("Very very very very very very very very very very "
// "very very very long poll question text"),
// MTP_vector<MTPPollAnswer>(answers))),
// MTP_string(captionText),
// MTP_long(rand_value<uint64>()),
// MTPnullMarkup,
// sentEntities
//)).done([=](const MTPUpdates &result) {
// applyUpdates(result);
//}).fail(
// base::duplicate(*failHandler)
//).afterRequest(history->sendRequestId
//).send();
AssertIsDebug();
auto answers = QVector<MTPPollAnswer>{
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<uint64>()),
MTP_flags(0),
MTP_string("Very very very very very very very very very very "
"very very very long poll question text"),
MTP_vector<MTPPollAnswer>(answers))),
MTP_string(captionText),
MTP_long(rand_value<uint64>()),
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);
@ -5343,6 +5344,81 @@ void ApiWrap::setSelfDestructDays(int days) {
_selfDestructChanges.fire_copy(days);
}
void ApiWrap::sendPollVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options) {
if (_pollVotesRequestIds.contains(itemId)) {
return;
}
const auto item = App::histItemById(itemId);
if (!item) {
return;
}
auto prepared = QVector<MTPbytes>();
prepared.reserve(options.size());
ranges::transform(
options,
ranges::back_inserter(prepared),
[](const QByteArray &option) { return MTP_bytes(option); });
const auto requestId = request(MTPmessages_SendVote(
item->history()->peer->input,
MTP_int(item->id),
MTP_vector<MTPbytes>(prepared)
)).done([=](const MTPUpdates &result) {
_pollVotesRequestIds.erase(itemId);
applyUpdates(result);
}).fail([=](const RPCError &error) {
_pollVotesRequestIds.erase(itemId);
}).send();
_pollVotesRequestIds.emplace(itemId, requestId);
}
void ApiWrap::closePoll(FullMsgId itemId) {
if (_pollCloseRequestIds.contains(itemId)) {
return;
}
const auto item = App::histItemById(itemId);
const auto media = item ? item->media() : nullptr;
const auto poll = media ? media->poll() : nullptr;
if (!poll) {
return;
}
const auto convert = [](const PollAnswer &answer) {
return MTP_pollAnswer(
MTP_string(answer.text),
MTP_bytes(answer.option));
};
auto answers = QVector<MTPPollAnswer>();
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<MTPPollAnswer>(answers))),
MTPReplyMarkup(),
MTPVector<MTPMessageEntity>()
)).done([=](const MTPUpdates &result) {
_pollCloseRequestIds.erase(itemId);
applyUpdates(result);
}).fail([=](const RPCError &error) {
_pollCloseRequestIds.erase(itemId);
}).send();
_pollCloseRequestIds.emplace(itemId, requestId);
}
void ApiWrap::readServerHistory(not_null<History*> history) {
if (history->unreadCount()) {
readServerHistoryForce(history);

View File

@ -377,6 +377,11 @@ public:
rpl::producer<int> selfDestructValue() const;
void saveSelfDestruct(int days);
void sendPollVotes(
FullMsgId itemId,
const std::vector<QByteArray> &options);
void closePoll(FullMsgId itemId);
~ApiWrap();
private:
@ -739,4 +744,7 @@ private:
std::optional<int> _selfDestructDays;
rpl::event_stream<int> _selfDestructChanges;
base::flat_map<FullMsgId, mtpRequestId> _pollVotesRequestIds;
base::flat_map<FullMsgId, mtpRequestId> _pollCloseRequestIds;
};

View File

@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_game.h"
#include "data/data_web_page.h"
#include "data/data_poll.h"
#include "lang/lang_keys.h"
#include "auth_session.h"
#include "layout.h"
@ -1092,7 +1093,7 @@ GameData *MediaGame::game() const {
}
QString MediaGame::pinnedTextSubstring() const {
auto title = _game->title;
const auto title = _game->title;
return lng_action_pinned_media_game(lt_game, title);
}
@ -1224,7 +1225,7 @@ QString MediaPoll::notificationText() const {
}
QString MediaPoll::pinnedTextSubstring() const {
return QString(); // #TODO polls
return QChar(171) + _poll->question + QChar(187);
}
TextWithEntities MediaPoll::clipboardText() const {

View File

@ -121,3 +121,7 @@ bool PollData::applyResultToAnswers(
return changed;
});
}
bool PollData::voted() const {
return ranges::find(answers, true, &PollAnswer::chosen) != end(answers);
}

View File

@ -32,6 +32,8 @@ struct PollData {
PollAnswer *answerByOption(const QByteArray &option);
const PollAnswer *answerByOption(const QByteArray &option) const;
bool voted() const;
PollId id = 0;
QString question;
std::vector<PollAnswer> answers;

View File

@ -492,12 +492,28 @@ webPageDescriptionStyle: defaultTextStyle;
webPagePhotoSize: 100px;
webPagePhotoDelta: 8px;
historyPollQuestionStyle: semiboldTextStyle;
historyPollAnswerStyle: defaultTextStyle;
historyPollQuestionFont: font(boxFontSize semibold);
historyPollQuestionStyle: TextStyle(defaultTextStyle) {
font: historyPollQuestionFont;
linkFont: historyPollQuestionFont;
linkFontOver: historyPollQuestionFont;
}
historyPollAnswerStyle: TextStyle(defaultTextStyle) {
font: boxTextFont;
linkFont: boxTextFont;
linkFontOver: boxTextFont;
}
historyPollQuestionTop: 7px;
historyPollSubtitleSkip: 5px;
historyPollAnswerPadding: margins(30px, 16px, 0px, 16px);
historyPollAnswersSkip: 5px;
historyPollPercentFont: semiboldFont;
historyPollPercentSkip: 5px;
historyPollPercentTop: 3px;
historyPollFillingMin: 8px;
historyPollFillingHeight: 6px;
historyPollFillingRadius: 2px;
historyPollFillingBottom: 10px;
boxAttachEmoji: IconButton(historyAttachEmoji) {
width: 30px;

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_message.h"
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_context_menu.h"
#include "ui/widgets/popup_menu.h"
#include "ui/image/image.h"
#include "ui/text_options.h"
@ -42,6 +43,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_media_types.h"
#include "data/data_document.h"
#include "data/data_poll.h"
#include "data/data_photo.h"
namespace {
@ -1638,6 +1640,22 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
}));
}
}
if (const auto media = item->media()) {
if (const auto poll = media->poll()) {
if (!poll->closed) {
if (poll->voted()) {
_menu->addAction(lang(lng_polls_retract), [=] {
Auth().api().sendPollVotes(itemId, {});
});
}
if (item->canStopPoll()) {
_menu->addAction(lang(lng_polls_stop), [=] {
HistoryView::StopPoll(itemId);
});
}
}
}
}
if (msg && view && !link && (view->hasVisibleText() || mediaHasTextForCopy)) {
_menu->addAction(lang(lng_context_copy_text), [=] {
copyContextText(itemId);

View File

@ -414,6 +414,28 @@ bool HistoryItem::allowsEdit(TimeId now) const {
return false;
}
bool HistoryItem::canStopPoll() const {
if (id < 0
|| Has<HistoryMessageVia>()
|| Has<HistoryMessageForwarded>()) {
return false;
}
const auto peer = _history->peer;
if (peer->isSelf()) {
return true;
} else if (const auto channel = peer->asChannel()) {
if (isPost() && channel->canEditMessages()) {
return true;
} else if (out()) {
return isPost() ? channel->canPublish() : channel->canWrite();
} else {
return false;
}
}
return out();
}
bool HistoryItem::canDelete() const {
if (isLogEntry() || (!IsServerMsgId(id) && serviceMsg())) {
return false;

View File

@ -211,6 +211,7 @@ public:
bool isPinned() const;
bool canPin() const;
bool canStopPoll() const;
virtual bool allowsForward() const;
virtual bool allowsEdit(TimeId now) const;
bool canDelete() const;

View File

@ -588,41 +588,22 @@ bool HistoryMessage::allowsForward() const {
return !_media || _media->allowsForward();
}
bool HistoryMessage::allowsEdit(TimeId now) const {
bool HistoryMessage::isTooOldForEdit(TimeId now) const {
const auto peer = _history->peer;
const auto messageToMyself = peer->isSelf();
const auto canPinInMegagroup = [&] {
if (const auto megagroup = peer->asMegagroup()) {
return megagroup->canPinMessages();
if (peer->isSelf()) {
return false;
} else if (const auto megagroup = peer->asMegagroup()) {
if (megagroup->canPinMessages()) {
return false;
}
return false;
}();
const auto messageTooOld = (messageToMyself || canPinInMegagroup)
? false
: (now - date() >= Global::EditTimeLimit());
if (id < 0 || messageTooOld) {
return false;
}
return (now - date() >= Global::EditTimeLimit());
}
if (Has<HistoryMessageVia>() || Has<HistoryMessageForwarded>()) {
return false;
}
if (_media && !_media->allowsEdit()) {
return false;
}
if (messageToMyself) {
return true;
}
if (const auto channel = _history->peer->asChannel()) {
if (isPost() && channel->canEditMessages()) {
return true;
}
if (out()) {
return isPost() ? channel->canPublish() : channel->canWrite();
}
}
return out();
bool HistoryMessage::allowsEdit(TimeId now) const {
return canStopPoll()
&& !isTooOldForEdit(now)
&& (!_media || _media->allowsEdit());
}
bool HistoryMessage::uploading() const {

View File

@ -148,6 +148,7 @@ private:
bool hasAdminBadge() const {
return _flags & MTPDmessage_ClientFlag::f_has_admin_badge;
}
bool isTooOldForEdit(TimeId now) const;
// For an invoice button we replace the button text with a "Receipt" key.
// It should show the receipt for the payed invoice. Still let mobile apps do that.

View File

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/media/history_media_poll.h"
#include "lang/lang_keys.h"
#include "layout.h"
#include "history/history.h"
#include "history/history_item.h"
#include "history/view/history_view_element.h"
@ -17,6 +16,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/text_options.h"
#include "data/data_media_types.h"
#include "data/data_poll.h"
#include "data/data_session.h"
#include "layout.h"
#include "auth_session.h"
#include "apiwrap.h"
#include "styles/style_history.h"
#include "styles/style_widgets.h"
@ -67,6 +70,7 @@ HistoryPoll::HistoryPoll(
: HistoryMedia(parent)
, _poll(poll)
, _question(st::msgMinWidth / 2) {
Auth().data().registerPollView(_poll, _parent);
}
QSize HistoryPoll::countOptimalSize() {
@ -98,11 +102,11 @@ QSize HistoryPoll::countOptimalSize() {
auto minHeight = st::historyPollQuestionTop
+ _question.minHeight()
+ st::historyPollSubtitleSkip
+ st::msgDateTextStyle.font->height
+ st::msgDateFont->height
+ st::historyPollAnswersSkip
+ answersHeight
+ st::msgPadding.bottom()
+ st::msgDateTextStyle.font->height
+ st::msgDateFont->height
+ st::msgPadding.bottom();
if (!isBubbleTop()) {
minHeight -= st::msgFileTopMinus;
@ -110,6 +114,19 @@ QSize HistoryPoll::countOptimalSize() {
return { maxWidth, minHeight };
}
int HistoryPoll::countAnswerHeight(
const Answer &answer,
int innerWidth) const {
const auto answerWidth = innerWidth
- st::historyPollAnswerPadding.left()
- st::historyPollAnswerPadding.right();
return st::historyPollAnswerPadding.top()
+ 2 * st::defaultCheckbox.textPosition.y()
+ answer.text.countHeight(answerWidth)
+ st::historyPollAnswerPadding.bottom()
+ st::lineWidth;
}
QSize HistoryPoll::countCurrentSize(int newWidth) {
const auto paddings = st::msgPadding.left() + st::msgPadding.right();
@ -118,27 +135,20 @@ QSize HistoryPoll::countCurrentSize(int newWidth) {
- st::msgPadding.left()
- st::msgPadding.right();
const auto answerWidth = innerWidth
- st::historyPollAnswerPadding.left()
- st::historyPollAnswerPadding.right();
const auto answersHeight = ranges::accumulate(ranges::view::all(
_answers
) | ranges::view::transform([&](const Answer &answer) {
return st::historyPollAnswerPadding.top()
+ 2 * st::defaultCheckbox.textPosition.y()
+ answer.text.countHeight(answerWidth)
+ st::historyPollAnswerPadding.bottom()
+ st::lineWidth;
return countAnswerHeight(answer, innerWidth);
}), 0);
auto newHeight = st::historyPollQuestionTop
+ _question.countHeight(innerWidth)
+ st::historyPollSubtitleSkip
+ st::msgDateTextStyle.font->height
+ st::msgDateFont->height
+ st::historyPollAnswersSkip
+ answersHeight
+ st::msgPadding.bottom()
+ st::msgDateTextStyle.font->height
+ st::msgDateFont->height
+ st::msgPadding.bottom();
if (!isBubbleTop()) {
newHeight -= st::msgFileTopMinus;
@ -171,15 +181,26 @@ void HistoryPoll::updateTexts() {
return result;
}) | ranges::to_vector;
_voted = ranges::find(
_poll->answers,
true,
[](const PollAnswer &answer) { return answer.chosen; }
) != end(_poll->answers);
for (auto &answer : _answers) {
answer.handler = createAnswerClickHandler(answer);
}
_closed = _poll->closed;
}
ClickHandlerPtr HistoryPoll::createAnswerClickHandler(
const Answer &answer) const {
const auto option = answer.option;
const auto itemId = _parent->data()->fullId();
return std::make_shared<LambdaClickHandler>([=] {
Auth().api().sendPollVotes(itemId, { option });
});
}
void HistoryPoll::updateVotes() const {
updateTotalVotes();
_voted = _poll->voted();
updateAnswerVotes();
}
void HistoryPoll::updateTotalVotes() const {
@ -199,19 +220,55 @@ void HistoryPoll::updateTotalVotes() const {
_totalVotesLabel.setText(st::msgDateTextStyle, string);
}
void HistoryPoll::updateAnswerVotesFromOriginal(
const Answer &answer,
const PollAnswer &original,
int totalVotes,
int maxVotes) const {
if (!_voted && !_closed) {
answer.votesPercent.clear();
} else if (answer.votes != original.votes
|| answer.votesPercent.isEmpty()) {
const auto percent = original.votes * 100 / totalVotes;
answer.votesPercent = QString::number(percent) + '%';
answer.votesPercentWidth = st::historyPollPercentFont->width(
answer.votesPercent);
}
answer.votes = original.votes;
answer.filling = answer.votes / float64(maxVotes);
}
void HistoryPoll::updateAnswerVotes() const {
if (_poll->answers.size() != _answers.size()
|| _poll->answers.empty()) {
return;
}
const auto totalVotes = std::max(1, _totalVotes);
const auto maxVotes = std::max(1, ranges::max_element(
_poll->answers,
ranges::less(),
&PollAnswer::votes)->votes);
auto &&answers = ranges::view::zip(_answers, _poll->answers);
for (auto &&[answer, original] : answers) {
updateAnswerVotesFromOriginal(
answer,
original,
totalVotes,
maxVotes);
}
}
void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
auto paintx = 0, painty = 0, paintw = width(), painth = height();
updateVotes();
auto outbg = _parent->hasOutLayout();
bool selected = (selection == FullSelection);
const auto outbg = _parent->hasOutLayout();
const auto selected = (selection == FullSelection);
const auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
auto padding = st::msgPadding;
const auto padding = st::msgPadding;
auto tshift = st::historyPollQuestionTop;
if (!isBubbleTop()) {
tshift -= st::msgFileTopMinus;
@ -224,34 +281,19 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time
p.setPen(regular);
_subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width());
tshift += st::msgDateTextStyle.font->height + st::historyPollAnswersSkip;
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
const auto aleft = padding.left() + st::historyPollAnswerPadding.left();
const auto awidth = paintw - st::historyPollAnswerPadding.left() - st::historyPollAnswerPadding.right();
for (const auto &answer : _answers) {
tshift += st::historyPollAnswerPadding.top();
{
PainterHighQualityEnabler hq(p);
const auto &st = st::defaultRadio;
auto pen = regular->p;
pen.setWidth(st.thickness);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
p.drawEllipse(QRectF(padding.left(), tshift, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.)));
}
tshift += st::defaultCheckbox.textPosition.y();
p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
answer.text.drawLeft(p, aleft, tshift, awidth, width());
tshift += answer.text.countHeight(awidth)
+ st::defaultCheckbox.textPosition.y()
+ st::historyPollAnswerPadding.bottom();
p.setOpacity(0.5);
p.fillRect(aleft, tshift, width() - aleft, st::lineWidth, regular);
tshift += st::lineWidth;
p.setOpacity(1.);
const auto height = paintAnswer(
p,
answer,
padding.left(),
tshift,
paintw,
width(),
selection,
ms);
tshift += height;
}
if (!_totalVotesLabel.isEmpty()) {
tshift += st::msgPadding.bottom();
@ -260,11 +302,101 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time
}
}
TextState HistoryPoll::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (QRect(0, 0, width(), height()).contains(point)) {
// result.link = _link;
return result;
int HistoryPoll::paintAnswer(
Painter &p,
const Answer &answer,
int left,
int top,
int width,
int outerWidth,
TextSelection selection,
TimeMs ms) const {
const auto result = countAnswerHeight(answer, width);
const auto bottom = top + result;
const auto outbg = _parent->hasOutLayout();
const auto selected = (selection == FullSelection);
const auto &regular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg);
const auto aleft = left + st::historyPollAnswerPadding.left();
const auto awidth = width
- st::historyPollAnswerPadding.left()
- st::historyPollAnswerPadding.right();
top += st::historyPollAnswerPadding.top();
if (_voted || _closed) {
p.setFont(st::historyPollPercentFont);
p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
const auto left = aleft
- answer.votesPercentWidth
- st::historyPollPercentSkip;
p.drawTextLeft(left, top + st::historyPollPercentTop, outerWidth, answer.votesPercent, answer.votesPercentWidth);
} else {
PainterHighQualityEnabler hq(p);
const auto &st = st::defaultRadio;
auto pen = regular->p;
pen.setWidth(st.thickness);
p.setPen(pen);
p.setBrush(Qt::NoBrush);
p.drawEllipse(QRectF(left, top, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.)));
}
top += st::defaultCheckbox.textPosition.y();
p.setPen(outbg ? st::webPageDescriptionOutFg : st::webPageDescriptionInFg);
answer.text.drawLeft(p, aleft, top, awidth, outerWidth);
if (_voted || _closed) {
auto &semibold = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(semibold);
const auto size = anim::interpolate(st::historyPollFillingMin, awidth, answer.filling);
const auto radius = st::historyPollFillingRadius;
const auto top = bottom - st::historyPollFillingBottom - st::historyPollFillingHeight;
p.drawRoundedRect(aleft, top, size, st::historyPollFillingHeight, radius, radius);
} else {
p.setOpacity(0.5);
p.fillRect(
aleft,
bottom - st::lineWidth,
outerWidth - aleft,
st::lineWidth,
regular);
p.setOpacity(1.);
}
return result;
}
TextState HistoryPoll::textState(QPoint point, StateRequest request) const {
auto result = TextState(_parent);
if (_voted || _closed) {
return result;
}
const auto padding = st::msgPadding;
auto paintw = width();
auto tshift = st::historyPollQuestionTop;
if (!isBubbleTop()) {
tshift -= st::msgFileTopMinus;
}
paintw -= padding.left() + padding.right();
tshift += _question.countHeight(paintw) + st::historyPollSubtitleSkip;
tshift += st::msgDateFont->height + st::historyPollAnswersSkip;
const auto awidth = paintw
- st::historyPollAnswerPadding.left()
- st::historyPollAnswerPadding.right();
for (const auto &answer : _answers) {
const auto height = countAnswerHeight(answer, paintw);
if (point.y() >= tshift && point.y() < tshift + height) {
result.link = answer.handler;
return result;
}
tshift += height;
}
return result;
}
HistoryPoll::~HistoryPoll() {
Auth().data().unregisterPollView(_poll, _parent);
}

View File

@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/media/history_media.h"
struct PollAnswer;
class HistoryPoll : public HistoryMedia {
public:
HistoryPoll(
@ -32,6 +34,8 @@ public:
return false;
}
~HistoryPoll();
private:
struct Answer {
Answer();
@ -39,19 +43,43 @@ private:
Text text;
QByteArray option;
mutable int votes = 0;
mutable int votesPercentWidth = 0;
mutable float64 filling = 0.;
mutable QString votesPercent;
mutable bool chosen = false;
ClickHandlerPtr handler;
};
QSize countOptimalSize() override;
QSize countCurrentSize(int newWidth) override;
int countAnswerHeight(const Answer &answer, int innerWidth) const;
[[nodiscard]] ClickHandlerPtr createAnswerClickHandler(
const Answer &answer) const;
void updateTexts();
void updateVotes() const;
void updateTotalVotes() const;
void updateAnswerVotes() const;
void updateAnswerVotesFromOriginal(
const Answer &answer,
const PollAnswer &original,
int totalVotes,
int maxVotes) const;
int paintAnswer(
Painter &p,
const Answer &answer,
int left,
int top,
int width,
int outerWidth,
TextSelection selection,
TimeMs ms) const;
not_null<PollData*> _poll;
int _pollVersion = 0;
mutable int _totalVotes = 0;
mutable bool _voted = false;
bool _closed = false;
Text _question;
Text _subtitle;

View File

@ -505,4 +505,12 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
return result;
}
void StopPoll(FullMsgId itemId) {
Ui::show(Box<ConfirmBox>(
lang(lng_polls_stop_warning),
lang(lng_polls_stop_sure),
lang(lng_cancel),
[=] { Ui::hideLayer(); Auth().api().closePoll(itemId); }));
}
} // namespace HistoryView

View File

@ -35,4 +35,6 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
not_null<ListWidget*> list,
const ContextMenuRequest &request);
void StopPoll(FullMsgId itemId);
} // namespace