From 554ba411f65765a9e82f1c70fe052ae6310be12b Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Tue, 24 Mar 2026 16:55:13 +0300 Subject: [PATCH] [poll-view] Added initial support of reply to poll answers. --- Telegram/Resources/langs/lang.strings | 2 + Telegram/SourceFiles/api/api_polls.cpp | 5 +- .../SourceFiles/core/click_handler_types.h | 3 +- Telegram/SourceFiles/data/data_histories.cpp | 7 +- Telegram/SourceFiles/data/data_msg_id.h | 6 +- Telegram/SourceFiles/data/data_poll.cpp | 1 + Telegram/SourceFiles/data/data_poll.h | 1 + .../history/history_inner_widget.cpp | 6 ++ .../history/history_item_components.cpp | 5 ++ .../history/history_item_components.h | 2 + .../history/history_item_helpers.cpp | 5 +- .../history_view_highlight_manager.cpp | 24 ++++- .../history/history_view_highlight_manager.h | 1 + .../SourceFiles/history/history_widget.cpp | 21 ++++- .../view/history_view_chat_section.cpp | 1 + .../view/history_view_context_menu.cpp | 6 ++ .../history/view/history_view_element.cpp | 66 ++++++++++++++ .../history/view/history_view_element.h | 5 ++ .../history/view/history_view_list_widget.cpp | 10 ++- .../history/view/history_view_reply.cpp | 27 +++++- .../history/view/media/history_view_poll.cpp | 89 ++++++++++++++++--- .../history/view/media/history_view_poll.h | 1 + Telegram/SourceFiles/ui/chat/chat_style.h | 1 + 23 files changed, 266 insertions(+), 29 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 362153a6b4..eb142ecb65 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -4973,6 +4973,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_context_reply_msg" = "Reply"; "lng_context_quote_and_reply" = "Quote & Reply"; "lng_context_reply_to_task" = "Reply to Task"; +"lng_context_reply_to_poll_option" = "Reply to Option"; "lng_context_edit_msg" = "Edit"; "lng_context_draw" = "Edit Image"; "lng_context_add_factcheck" = "Add Fact Check"; @@ -5214,6 +5215,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_preview_reply_to" = "Reply to {name}"; "lng_preview_reply_to_quote" = "Reply to quote from {name}"; "lng_preview_reply_to_task" = "Reply to task from {title}"; +"lng_preview_reply_to_poll_option" = "Reply to poll option from {title}"; "lng_suggest_bar_title" = "Suggest a Post Below"; "lng_suggest_bar_text" = "Click to offer a price for publishing."; diff --git a/Telegram/SourceFiles/api/api_polls.cpp b/Telegram/SourceFiles/api/api_polls.cpp index b80c408fe6..874f792907 100644 --- a/Telegram/SourceFiles/api/api_polls.cpp +++ b/Telegram/SourceFiles/api/api_polls.cpp @@ -220,10 +220,13 @@ void Polls::reloadResults(not_null item) { if (!item->isRegular() || _pollReloadRequestIds.contains(itemId)) { return; } + const auto media = item->media(); + const auto poll = media ? media->poll() : nullptr; + const auto pollHash = poll ? poll->hash : uint64(0); const auto requestId = _api.request(MTPmessages_GetPollResults( item->history()->peer->input(), MTP_int(item->id), - MTP_long(0) + MTP_long(pollHash) )).done([=](const MTPUpdates &result) { _pollReloadRequestIds.erase(itemId); _session->updates().applyUpdates(result); diff --git a/Telegram/SourceFiles/core/click_handler_types.h b/Telegram/SourceFiles/core/click_handler_types.h index 0aebaf8416..dc58aa806f 100644 --- a/Telegram/SourceFiles/core/click_handler_types.h +++ b/Telegram/SourceFiles/core/click_handler_types.h @@ -19,7 +19,8 @@ constexpr auto kReactionsCountEmojiProperty = 0x05; constexpr auto kDocumentFilenameTooltipProperty = 0x06; constexpr auto kPhoneNumberLinkProperty = 0x07; constexpr auto kTodoListItemIdProperty = 0x08; -constexpr auto kFastShareProperty = 0x09; +constexpr auto kPollOptionProperty = 0x09; +constexpr auto kFastShareProperty = 0x0a; namespace Ui { class Show; diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index bc4a40e5f5..534ae0dd24 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -91,7 +91,10 @@ MTPInputReplyTo ReplyToForMTP( | (quoteEntities.v.isEmpty() ? Flag() : Flag::f_quote_entities) - | (replyTo.todoItemId ? Flag::f_todo_item_id : Flag())), + | (replyTo.todoItemId ? Flag::f_todo_item_id : Flag()) + | (replyTo.pollOption.isEmpty() + ? Flag() + : Flag::f_poll_option)), MTP_int(replyTo.messageId ? replyTo.messageId.msg : 0), MTP_int(replyTo.topicRootId), (external @@ -104,7 +107,7 @@ MTPInputReplyTo ReplyToForMTP( ? history->owner().peer(replyToMonoforumPeerId)->input() : MTPInputPeer()), MTP_int(replyTo.todoItemId), - MTPbytes()); // poll_option + MTP_bytes(replyTo.pollOption)); } else if (history->peer->amMonoforumAdmin() && replyTo.monoforumPeerId) { const auto replyToMonoforumPeer = replyTo.monoforumPeerId diff --git a/Telegram/SourceFiles/data/data_msg_id.h b/Telegram/SourceFiles/data/data_msg_id.h index bd8d183d36..150393645f 100644 --- a/Telegram/SourceFiles/data/data_msg_id.h +++ b/Telegram/SourceFiles/data/data_msg_id.h @@ -176,9 +176,10 @@ struct MessageHighlightId { TextWithEntities quote; int quoteOffset = 0; int todoItemId = 0; + QByteArray pollOption; [[nodiscard]] bool empty() const { - return quote.empty() && !todoItemId; + return quote.empty() && !todoItemId && pollOption.isEmpty(); } [[nodiscard]] friend inline bool operator==( const MessageHighlightId &a, @@ -193,9 +194,10 @@ struct FullReplyTo { PeerId monoforumPeerId = 0; int quoteOffset = 0; int todoItemId = 0; + QByteArray pollOption; [[nodiscard]] MessageHighlightId highlight() const { - return { quote, quoteOffset, todoItemId }; + return { quote, quoteOffset, todoItemId, pollOption }; } [[nodiscard]] bool replying() const { return messageId || (storyId && storyId.peer); diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index 3785d40c25..cfe3fde142 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -161,6 +161,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) { } } } + hash = poll.vhash().v; ++version; return true; } diff --git a/Telegram/SourceFiles/data/data_poll.h b/Telegram/SourceFiles/data/data_poll.h index fe458baff2..59f25c4413 100644 --- a/Telegram/SourceFiles/data/data_poll.h +++ b/Telegram/SourceFiles/data/data_poll.h @@ -105,6 +105,7 @@ struct PollData { TimeId closeDate = 0; int totalVoters = 0; int version = 0; + uint64 hash = 0; static constexpr auto kMaxOptions = 32; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 79dff4163f..98f226fdd9 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -2501,6 +2501,9 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { const auto todoListTaskId = link ? link->property(kTodoListItemIdProperty).toInt() : 0; + const auto pollOptionLink = link + ? link->property(kPollOptionProperty).toByteArray() + : QByteArray(); const auto session = &this->session(); _whoReactedMenuLifetime.destroy(); if (!clickedReaction.empty() && leaderOrSelf) { @@ -2909,6 +2912,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { ? tr::lng_context_quote_and_reply : todoListTaskId ? tr::lng_context_reply_to_task + : !pollOptionLink.isEmpty() + ? tr::lng_context_reply_to_poll_option : tr::lng_context_reply_msg)( tr::now, Ui::Text::FixAmpersandInAction); @@ -2920,6 +2925,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) { .quote = selected.highlight.quote, .quoteOffset = selected.highlight.quoteOffset, .todoItemId = todoListTaskId, + .pollOption = pollOptionLink, }); if (!selected.highlight.quote.empty()) { _widget->clearSelected(); diff --git a/Telegram/SourceFiles/history/history_item_components.cpp b/Telegram/SourceFiles/history/history_item_components.cpp index 5655bff23a..d671523e89 100644 --- a/Telegram/SourceFiles/history/history_item_components.cpp +++ b/Telegram/SourceFiles/history/history_item_components.cpp @@ -361,6 +361,8 @@ ReplyFields ReplyFields::clone(not_null parent) const { .messageId = messageId, .topMessageId = topMessageId, .storyId = storyId, + .todoItemId = todoItemId, + .pollOption = pollOption, .quoteOffset = quoteOffset, .manualQuote = manualQuote, .topicPost = topicPost, @@ -389,6 +391,7 @@ ReplyFields ReplyFieldsFromMTP( result.topicPost = data.is_forum_topic() ? 1 : 0; } result.todoItemId = data.vtodo_item_id().value_or_empty(); + result.pollOption = data.vpoll_option().value_or_empty(); if (const auto header = data.vreply_from()) { const auto &data = header->data(); result.externalPostAuthor @@ -443,6 +446,8 @@ FullReplyTo ReplyToFromMTP( data.vquote_entities().value_or_empty()), }; result.quoteOffset = data.vquote_offset().value_or_empty(); + result.todoItemId = data.vtodo_item_id().value_or_empty(); + result.pollOption = data.vpoll_option().value_or_empty(); return result; }, [&](const MTPDinputReplyToStory &data) { if (const auto parsed = Data::PeerFromInputMTP( diff --git a/Telegram/SourceFiles/history/history_item_components.h b/Telegram/SourceFiles/history/history_item_components.h index f42041c9da..348cd83e1d 100644 --- a/Telegram/SourceFiles/history/history_item_components.h +++ b/Telegram/SourceFiles/history/history_item_components.h @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "data/data_cloud_file.h" +#include "data/data_poll.h" #include "history/history_item.h" #include "spellcheck/spellcheck_types.h" // LanguageId. #include "ui/empty_userpic.h" @@ -286,6 +287,7 @@ struct ReplyFields { MsgId topMessageId = 0; StoryId storyId = 0; int todoItemId = 0; + QByteArray pollOption; uint32 quoteOffset : 30 = 0; uint32 manualQuote : 1 = 0; uint32 topicPost : 1 = 0; diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 5b113aa54f..e88972991c 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -906,6 +906,9 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { ? Flag() : Flag::f_quote_entities) | (replyTo.todoItemId ? Flag::f_todo_item_id : Flag()) + | (replyTo.pollOption.isEmpty() + ? Flag() + : Flag::f_poll_option) | (topicPost ? Flag::f_forum_topic : Flag())), MTP_int(replyTo.messageId.msg), peerToMTP(externalPeerId), @@ -916,7 +919,7 @@ MTPMessageReplyHeader NewMessageReplyHeader(const Api::SendAction &action) { quoteEntities, MTP_int(replyTo.quoteOffset), MTP_int(replyTo.todoItemId), - MTPbytes()); // poll_option + MTP_bytes(replyTo.pollOption)); } return MTPMessageReplyHeader(); } diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp index 5ff3f71a6d..73dc510ac5 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.cpp +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.cpp @@ -66,6 +66,7 @@ Ui::ChatPaintHighlight ElementHighlighter::state( auto result = _animation.state(); result.range = _highlighted.part; result.todoItemId = _highlighted.todoListId; + result.pollOption = _highlighted.pollOption; return result; } return {}; @@ -90,20 +91,37 @@ ElementHighlighter::Highlight ElementHighlighter::computeHighlight( leaderId, leaderView->selectionFromQuote(quote), quote.highlight.todoItemId, + quote.highlight.pollOption, }; } } - return { leaderId, {}, quote.highlight.todoItemId }; + return { + leaderId, + {}, + quote.highlight.todoItemId, + quote.highlight.pollOption, + }; } else if (quote.highlight.quote.empty()) { - return { item->fullId(), {}, quote.highlight.todoItemId }; + return { + item->fullId(), + {}, + quote.highlight.todoItemId, + quote.highlight.pollOption, + }; } else if (const auto view = _viewForItem(item)) { return { item->fullId(), view->selectionFromQuote(quote), quote.highlight.todoItemId, + quote.highlight.pollOption, }; } - return { item->fullId(), {}, quote.highlight.todoItemId }; + return { + item->fullId(), + {}, + quote.highlight.todoItemId, + quote.highlight.pollOption, + }; } void ElementHighlighter::highlight(Highlight data) { diff --git a/Telegram/SourceFiles/history/history_view_highlight_manager.h b/Telegram/SourceFiles/history/history_view_highlight_manager.h index 08ac68e44d..2619db2d91 100644 --- a/Telegram/SourceFiles/history/history_view_highlight_manager.h +++ b/Telegram/SourceFiles/history/history_view_highlight_manager.h @@ -66,6 +66,7 @@ private: FullMsgId itemId; TextSelection part; int todoListId = 0; + QByteArray pollOption; explicit operator bool() const { return itemId.operator bool(); diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 57eb8b67db..96c929ac0b 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -1592,12 +1592,18 @@ int HistoryWidget::itemTopForHighlight( if (heightLeft >= 0) { return std::max(itemTop - (heightLeft / 2), 0); } else if (const auto highlight = itemHighlight(item) - ; (!highlight.range.empty() || highlight.todoItemId) + ; (!highlight.range.empty() + || highlight.todoItemId + || !highlight.pollOption.isEmpty()) && !IsSubGroupSelection(highlight.range)) { const auto sel = highlight.range; const auto single = st::messageTextStyle.font->height; const auto todoy = sel.empty() - ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + ? (highlight.todoItemId + ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + : !highlight.pollOption.isEmpty() + ? HistoryView::FindViewPollOptionY(view, highlight.pollOption) + : 0) : 0; const auto begin = sel.empty() ? (todoy - 4 * single) @@ -9656,7 +9662,10 @@ void HistoryWidget::updateReplyEditText(not_null item) { .repaint = [=] { updateField(); }, }); const auto text = [&] { - const auto media = _replyTo.todoItemId ? item->media() : nullptr; + const auto media = (_replyTo.todoItemId + || !_replyTo.pollOption.isEmpty()) + ? item->media() + : nullptr; if (const auto todolist = media ? media->todolist() : nullptr) { const auto i = ranges::find( todolist->items, @@ -9666,6 +9675,12 @@ void HistoryWidget::updateReplyEditText(not_null item) { return i->text; } } + if (const auto poll = media ? media->poll() : nullptr) { + if (const auto answer = poll->answerByOption( + _replyTo.pollOption)) { + return answer->text; + } + } return (_editMsgId || _replyTo.quote.empty()) ? item->inReplyText() : _replyTo.quote; diff --git a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp index a4f4b3509a..7b36e69aa7 100644 --- a/Telegram/SourceFiles/history/view/history_view_chat_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_chat_section.cpp @@ -1065,6 +1065,7 @@ void ChatWidget::setupSwipeReplyAndBack() { .quote = selected.highlight.quote, .quoteOffset = selected.highlight.quoteOffset, .todoItemId = selected.highlight.todoItemId, + .pollOption = selected.highlight.pollOption, }); }; return result; diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 7753baee20..f947c46cba 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -623,9 +623,14 @@ bool AddReplyToMessageAction( const auto todoListTaskId = request.link ? request.link->property(kTodoListItemIdProperty).toInt() : 0; + const auto pollOption = request.link + ? request.link->property(kPollOptionProperty).toByteArray() + : QByteArray(); const auto "e = request.quote; auto text = (todoListTaskId ? tr::lng_context_reply_to_task + : !pollOption.isEmpty() + ? tr::lng_context_reply_to_poll_option : quote.highlight.quote.empty() ? tr::lng_context_reply_msg : tr::lng_context_quote_and_reply)( @@ -637,6 +642,7 @@ bool AddReplyToMessageAction( .quote = quote.highlight.quote, .quoteOffset = quote.highlight.quoteOffset, .todoItemId = todoListTaskId, + .pollOption = pollOption, }, base::IsCtrlPressed()); }, &st::menuIconReply); return true; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 62f63e4306..eb69ee0409 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -3059,6 +3059,72 @@ int FindViewTaskY(not_null view, int taskId, int yfrom) { return origin.y() + (yfrom + ytill) / 2; } +int FindViewPollOptionY( + not_null view, + const QByteArray &option, + int yfrom) { + auto request = HistoryView::StateRequest(); + request.flags = Ui::Text::StateRequest::Flag::LookupLink; + const auto single = st::messageTextStyle.font->height; + const auto inner = view->innerGeometry(); + const auto origin = inner.topLeft(); + if (origin.y() < 0 + || origin.y() + inner.height() > view->height() + || inner.height() <= 0) { + return yfrom; + } + const auto media = view->data()->media(); + const auto poll = media ? media->poll() : nullptr; + if (!poll) { + return yfrom; + } + const auto &answers = poll->answers; + const auto indexOf = [&](const QByteArray &opt) -> int { + return int(ranges::find( + answers, opt, &PollAnswer::option) - begin(answers)); + }; + const auto index = indexOf(option); + const auto count = int(answers.size()); + if (index == count) { + return yfrom; + } + yfrom = std::max(yfrom - origin.y(), 0); + auto ytill = inner.height() - 1; + const auto middle = (yfrom + ytill) / 2; + const auto fory = [&](int y) { + const auto state = view->textState(origin + QPoint(0, y), request); + const auto &link = state.link; + const auto opt = link + ? link->property(kPollOptionProperty).toByteArray() + : QByteArray(); + const auto idx = !opt.isEmpty() ? indexOf(opt) : count; + return (idx < count) ? idx : (y < middle) ? -1 : count; + }; + auto indexfrom = fory(yfrom); + auto indextill = fory(ytill); + if ((yfrom >= ytill) || (indexfrom >= index)) { + return origin.y() + yfrom; + } else if (indextill <= index) { + return origin.y() + ytill; + } + while (ytill - yfrom >= 2 * single) { + const auto mid = (yfrom + ytill) / 2; + const auto found = fory(mid); + if (found == index + || indexfrom > found + || indextill < found) { + return origin.y() + mid; + } else if (found < index) { + yfrom = mid; + indexfrom = found; + } else { + ytill = mid; + indextill = found; + } + } + return origin.y() + (yfrom + ytill) / 2; +} + Window::SessionController *ExtractController(const ClickContext &context) { const auto my = context.other.value(); if (const auto controller = my.sessionWindow.get()) { diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 9b789387c5..6b1a96157e 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -789,6 +789,11 @@ private: int taskId, int yfrom = 0); +[[nodiscard]] int FindViewPollOptionY( + not_null view, + const QByteArray &option, + int yfrom = 0); + [[nodiscard]] Window::SessionController *ExtractController( const ClickContext &context); diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index 4ef8b40ab3..5783f734ef 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -764,12 +764,18 @@ std::optional ListWidget::scrollTopForView( if (heightLeft >= 0) { return std::max(top - (heightLeft / 2), 0); } else if (const auto highlight = _highlighter.state(view->data()) - ; (!highlight.range.empty() || highlight.todoItemId) + ; (!highlight.range.empty() + || highlight.todoItemId + || !highlight.pollOption.isEmpty()) && !IsSubGroupSelection(highlight.range)) { const auto sel = highlight.range; const auto single = st::messageTextStyle.font->height; const auto todoy = sel.empty() - ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + ? (highlight.todoItemId + ? HistoryView::FindViewTaskY(view, highlight.todoItemId) + : !highlight.pollOption.isEmpty() + ? HistoryView::FindViewPollOptionY(view, highlight.pollOption) + : 0) : 0; const auto begin = sel.empty() ? (todoy - 4 * single) diff --git a/Telegram/SourceFiles/history/view/history_view_reply.cpp b/Telegram/SourceFiles/history/view/history_view_reply.cpp index 04a4cae4ff..11bdff8d40 100644 --- a/Telegram/SourceFiles/history/view/history_view_reply.cpp +++ b/Telegram/SourceFiles/history/view/history_view_reply.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "data/data_channel.h" #include "data/data_peer.h" +#include "data/data_poll.h" #include "data/data_session.h" #include "data/data_story.h" #include "data/data_todo_list.h" @@ -310,7 +311,8 @@ void Reply::update( const auto item = view->data(); const auto &fields = data->fields(); const auto message = data->resolvedMessage.get(); - const auto messageMedia = (message && fields.todoItemId) + const auto messageMedia = (message + && (fields.todoItemId || !fields.pollOption.isEmpty())) ? message->media() : nullptr; const auto messageTodoList = messageMedia @@ -326,6 +328,12 @@ void Reply::update( && taskIndex < messageTodoList->items.size()) ? &messageTodoList->items[taskIndex] : nullptr; + const auto messagePoll = messageMedia + ? messageMedia->poll() + : nullptr; + const auto pollAnswer = messagePoll + ? messagePoll->answerByOption(fields.pollOption) + : nullptr; const auto story = data->resolvedStory.get(); const auto externalMedia = fields.externalMedia.get(); if (!_externalSender) { @@ -374,6 +382,11 @@ void Reply::update( .image = MakeTaskImage(), .margin = QMargins(0, st::lineWidth, st::lineWidth, 0), })).append(task->text) + : pollAnswer + ? Ui::Text::Colorized(helper.image({ + .image = MakeTaskImage(), + .margin = QMargins(0, st::lineWidth, st::lineWidth, 0), + })).append(pollAnswer->text) : (message && (fields.quote.empty() || !fields.manualQuote)) ? message->inReplyText() : !fields.quote.empty() @@ -431,6 +444,7 @@ void Reply::setLinkFrom( .quote = fields.manualQuote ? fields.quote : TextWithEntities(), .quoteOffset = int(fields.quoteOffset), .todoItemId = fields.todoItemId, + .pollOption = fields.pollOption, }; const auto returnToId = view->data()->fullId(); const auto externalLink = [=](ClickContext context) { @@ -1033,13 +1047,22 @@ TextWithEntities Reply::ComposePreviewName( } return to->author(); }(); - if (const auto media = replyTo.todoItemId ? to->media() : nullptr) { + if (const auto media = (replyTo.todoItemId + || !replyTo.pollOption.isEmpty()) + ? to->media() + : nullptr) { if (const auto todolist = media->todolist()) { return tr::lng_preview_reply_to_task( tr::now, lt_title, todolist->title, tr::marked); + } else if (const auto poll = media->poll()) { + return tr::lng_preview_reply_to_poll_option( + tr::now, + lt_title, + poll->question, + tr::marked); } } const auto toPeer = to->history()->peer; diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 4bc4f196d5..e40ca035f8 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -282,6 +282,11 @@ PollThumbnailData MakePollThumbnail( }); }); } + if (result.handler) { + result.handler->setProperty( + kPollOptionProperty, + answer.option); + } return result; } @@ -1202,17 +1207,29 @@ void Poll::updateAnswers() { ClickHandlerPtr Poll::createAnswerClickHandler( const Answer &answer) { const auto option = answer.option; + auto result = ClickHandlerPtr(); if (_flags & PollData::Flag::MultiChoice) { - return std::make_shared(crl::guard(this, [=] { - toggleMultiOption(option); + result = std::make_shared(crl::guard(this, [=] { + if (canVote()) { + toggleMultiOption(option); + } else if (showVotes()) { + showAnswerVotesTooltip(option); + } + })); + } else { + result = std::make_shared(crl::guard(this, [=] { + if (canVote()) { + _votedFromHere = true; + history()->session().api().polls().sendVotes( + _parent->data()->fullId(), + { option }); + } else if (showVotes()) { + showAnswerVotesTooltip(option); + } })); } - return std::make_shared(crl::guard(this, [=] { - _votedFromHere = true; - history()->session().api().polls().sendVotes( - _parent->data()->fullId(), - { option }); - })); + result->setProperty(kPollOptionProperty, option); + return result; } void Poll::toggleMultiOption(const QByteArray &option) { @@ -1255,6 +1272,25 @@ void Poll::sendMultiOptions() { } } +void Poll::showAnswerVotesTooltip(const QByteArray &option) { + const auto answer = _poll->answerByOption(option); + if (!answer) { + return; + } + const auto quiz = _poll->quiz(); + const auto text = answer->votes + ? (quiz + ? tr::lng_polls_answers_count + : tr::lng_polls_votes_count)( + tr::now, + lt_count_decimal, + answer->votes) + : (quiz + ? tr::lng_polls_answers_none + : tr::lng_polls_votes_none)(tr::now); + _parent->delegate()->elementShowTooltip({ text }, [] {}); +} + void Poll::showResults() { _parent->delegate()->elementShowPollResults( _poll, @@ -1919,6 +1955,30 @@ int Poll::paintAnswer( int outerWidth, const PaintContext &context) const { const auto height = countAnswerHeight(answer, width); + if (!context.highlight.pollOption.isEmpty() + && context.highlight.pollOption == answer.option + && context.highlight.collapsion > 0.) { + const auto to = context.highlightInterpolateTo; + const auto toProgress = (1. - context.highlight.collapsion); + if (toProgress >= 1.) { + context.highlightPathCache->addRect(to); + } else if (toProgress <= 0.) { + context.highlightPathCache->addRect( + 0, + top, + this->width(), + height); + } else { + const auto lerp = [=](int from, int to) { + return from + (to - from) * toProgress; + }; + context.highlightPathCache->addRect( + lerp(0, to.x()), + lerp(top, to.y()), + lerp(this->width(), to.width()), + lerp(height, to.height())); + } + } const auto stm = context.messageStyle(); const auto &answerPadding = answer.thumbnail ? st::historyPollAnswerPadding @@ -2623,10 +2683,13 @@ TextState Poll::textState(QPoint point, StateRequest request) const { media, media).contains(point)) { result.link = answer.mediaHandler; - } else if (can) { - _lastLinkPoint = point; + } else { + if (can) { + _lastLinkPoint = point; + } result.link = answer.handler; - } else if (show) { + } + if (!can && show) { result.customTooltip = true; using Flag = Ui::Text::StateRequest::Flag; if (request.flags & Flag::LookupCustomTooltip) { @@ -2734,7 +2797,9 @@ void Poll::clickHandlerPressedChanged( handler, &Answer::handler); if (i != end(_answers)) { - toggleRipple(*i, pressed); + if (canVote()) { + toggleRipple(*i, pressed); + } } else if (handler == _sendVotesLink || handler == _showResultsLink) { toggleLinkRipple(pressed); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.h b/Telegram/SourceFiles/history/view/media/history_view_poll.h index 30a5330ed3..6942085e6a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.h +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.h @@ -207,6 +207,7 @@ private: void toggleMultiOption(const QByteArray &option); void sendMultiOptions(); void showResults(); + void showAnswerVotesTooltip(const QByteArray &option); void checkQuizAnswered(); void showSolution() const; void solutionToggled( diff --git a/Telegram/SourceFiles/ui/chat/chat_style.h b/Telegram/SourceFiles/ui/chat/chat_style.h index 118d5486b7..0be8f9f087 100644 --- a/Telegram/SourceFiles/ui/chat/chat_style.h +++ b/Telegram/SourceFiles/ui/chat/chat_style.h @@ -183,6 +183,7 @@ struct ChatPaintHighlight { float64 collapsion = 0.; TextSelection range; int todoItemId = 0; + QByteArray pollOption; }; struct ChatPaintContext {