From 39ce6a1a0a0dbfd9eb428eb75125dae5591db0ae Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 20 Mar 2026 15:02:05 +0300 Subject: [PATCH] [poll-view] Added author ability to view results without voting. --- Telegram/Resources/langs/lang.strings | 5 + .../history/view/media/history_view_poll.cpp | 115 ++++++++++++++++-- .../history/view/media/history_view_poll.h | 6 + Telegram/SourceFiles/ui/chat/chat.style | 4 + 4 files changed, 117 insertions(+), 13 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 88dff33ea9..f85e273fa2 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6842,6 +6842,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_answers_none" = "No answers"; "lng_polls_submit_votes" = "Vote"; "lng_polls_view_results" = "View results"; +"lng_polls_view_votes#one" = "View Votes ({count})"; +"lng_polls_view_votes#other" = "View Votes ({count})"; +"lng_polls_admin_votes#one" = "{count} vote {arrow}"; +"lng_polls_admin_votes#other" = "{count} votes {arrow}"; +"lng_polls_admin_back_vote" = "{arrow} Vote"; "lng_polls_ends_in_days#one" = "ends in {count} day"; "lng_polls_ends_in_days#other" = "ends in {count} days"; "lng_polls_results_in_days#one" = "results in {count} day"; diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index ac134cad0a..70dc396307 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -448,6 +448,28 @@ Poll::Poll( std::make_shared(crl::guard( this, [=] { sendMultiOptions(); }))) +, _adminVotesLink( + std::make_shared(crl::guard( + this, + [=] { + if (_flags & PollData::Flag::PublicVotes) { + showResults(); + } else { + saveStateInAnimation(); + _adminShowResults = true; + updateAnswerVotes(); + startAnswersAnimation(); + } + }))) +, _adminBackVoteLink( + std::make_shared(crl::guard( + this, + [=] { + saveStateInAnimation(); + _adminShowResults = false; + updateAnswerVotes(); + startAnswersAnimation(); + }))) , _closeTimer([=] { repaint(); }) { if (!consumed.text.isEmpty()) { updateDescription(); @@ -508,12 +530,21 @@ QSize Poll::countOptimalSize() { } bool Poll::showVotes() const { + if (_adminShowResults) { + return true; + } if (_flags & PollData::Flag::HideResultsUntilClose) { return (_flags & PollData::Flag::Closed) || _parent->data()->out(); } return _voted || (_flags & PollData::Flag::Closed); } +bool Poll::isAuthorNotVoted() const { + return _parent->data()->out() + && !_voted + && !(_flags & PollData::Flag::Closed); +} + bool Poll::canVote() const { return !showVotes() && _parent->data()->isRegular(); } @@ -650,6 +681,15 @@ void Poll::updateTexts() { ? tr::lng_polls_public(tr::now) : tr::lng_polls_anonymous(tr::now)))); } + if (_adminBackVoteLabel.isEmpty()) { + _adminBackVoteLabel.setMarkedText( + st::semiboldTextStyle, + tr::lng_polls_admin_back_vote( + tr::now, + lt_arrow, + Ui::Text::IconEmoji(&st::textBackIconEmoji), + tr::marked)); + } updateRecentVoters(); updateAnswers(); updateAttachedMedia(); @@ -1293,6 +1333,7 @@ void Poll::updateVotes() { } } else { _votedFromHere = false; + _hasSelected = false; } } updateAnswerVotes(); @@ -1338,6 +1379,15 @@ void Poll::updateTotalVotes() { lt_count_short, _totalVotes); _totalVotesLabel.setText(st::msgDateTextStyle, string); + _adminVotesLabel.setMarkedText( + st::semiboldTextStyle, + tr::lng_polls_admin_votes( + tr::now, + lt_count, + _totalVotes, + lt_arrow, + Ui::Text::IconEmoji(&st::textMoreIconEmoji), + tr::marked)); } void Poll::updateAnswerVotesFromOriginal( @@ -1646,7 +1696,25 @@ void Poll::paintBottom( + st::msgPadding.bottom() + st::historyPollBottomButtonTop; const auto stm = context.messageStyle(); - if (showVotersCount()) { + if (isAuthorNotVoted() && !_adminShowResults && !canSendVotes()) { + p.setPen(stm->msgFileThumbLinkFg); + const auto labelWidth = _adminVotesLabel.maxWidth(); + _adminVotesLabel.drawLeft( + p, + left + (paintw - labelWidth) / 2, + stringtop, + labelWidth, + width()); + } else if (_adminShowResults && isAuthorNotVoted()) { + p.setPen(stm->msgFileThumbLinkFg); + const auto backw = _adminBackVoteLabel.maxWidth(); + _adminBackVoteLabel.drawLeft( + p, + left + (paintw - backw) / 2, + stringtop, + backw, + width()); + } else if (showVotersCount()) { p.setPen(stm->msgDateFg); const auto timerText = closeTimerText(); if (timerText.isEmpty()) { @@ -1710,8 +1778,13 @@ void Poll::paintBottom( p.setFont(st::semiboldFont); p.setPen(link ? stm->msgFileThumbLinkFg : stm->msgDateFg); const auto string = showVotes() - ? tr::lng_polls_view_results(tr::now, tr::upper) - : tr::lng_polls_submit_votes(tr::now, tr::upper); + ? ((_flags & PollData::Flag::PublicVotes) + ? tr::lng_polls_view_votes( + tr::now, + lt_count, + _totalVotes) + : tr::lng_polls_view_results(tr::now)) + : tr::lng_polls_submit_votes(tr::now); const auto stringw = st::semiboldFont->width(string); p.drawTextLeft( left + (paintw - stringw) / 2, @@ -2689,16 +2762,29 @@ TextState Poll::textState(QPoint point, StateRequest request) const { } tshift += height; } - if (!showVotersCount()) { - const auto link = showVotes() - ? _showResultsLink - : canSendVotes() - ? _sendVotesLink - : nullptr; - if (link) { - const auto linkHeight = bottomButtonHeight(); - const auto linkTop = height() - linkHeight; + { + const auto linkHeight = bottomButtonHeight(); + const auto linkTop = height() - linkHeight; + if (isAuthorNotVoted() && !_adminShowResults && !canSendVotes()) { if (QRect(0, linkTop, width(), linkHeight).contains(point)) { + _lastLinkPoint = point; + result.link = _adminVotesLink; + return result; + } + } else if (_adminShowResults && isAuthorNotVoted()) { + if (QRect(0, linkTop, width(), linkHeight).contains(point)) { + _lastLinkPoint = point; + result.link = _adminBackVoteLink; + return result; + } + } else if (!showVotersCount()) { + const auto link = showVotes() + ? _showResultsLink + : canSendVotes() + ? _sendVotesLink + : nullptr; + if (link + && QRect(0, linkTop, width(), linkHeight).contains(point)) { _lastLinkPoint = point; result.link = link; return result; @@ -2779,7 +2865,10 @@ void Poll::clickHandlerPressedChanged( if (canVote()) { toggleRipple(*i, pressed); } - } else if (handler == _sendVotesLink || handler == _showResultsLink) { + } else if (handler == _sendVotesLink + || handler == _showResultsLink + || handler == _adminVotesLink + || handler == _adminBackVoteLink) { toggleLinkRipple(pressed); } if (_attachedMediaAttach) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.h b/Telegram/SourceFiles/history/view/media/history_view_poll.h index 688aaefd7b..99862e97f1 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.h +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.h @@ -86,6 +86,7 @@ private: [[nodiscard]] bool showVotes() const; [[nodiscard]] bool canVote() const; [[nodiscard]] bool canSendVotes() const; + [[nodiscard]] bool isAuthorNotVoted() const; void updateDescription(); void updateAttachedMedia(); [[nodiscard]] int countTopContentSkip(int pollWidth = 0) const; @@ -250,8 +251,12 @@ private: std::vector _answers; Ui::Text::String _totalVotesLabel; + Ui::Text::String _adminVotesLabel; + Ui::Text::String _adminBackVoteLabel; ClickHandlerPtr _showResultsLink; ClickHandlerPtr _sendVotesLink; + ClickHandlerPtr _adminVotesLink; + ClickHandlerPtr _adminBackVoteLink; mutable ClickHandlerPtr _showSolutionLink; mutable std::unique_ptr _linkRipple; mutable int _linkRippleShift = 0; @@ -279,6 +284,7 @@ private: bool _anyAnswerHasMedia = false; bool _votedFromHere = false; mutable bool _wrongAnswerAnimated = false; + mutable bool _adminShowResults = false; }; diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index 01a22e9399..b20ad42a66 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -372,6 +372,10 @@ textMoreIconEmoji: IconEmoji { icon: topicButtonArrow; padding: margins(-2px, 5px, 0px, 0px); } +textBackIconEmoji: IconEmoji { + icon: icon{{ "dialogs/dialogs_topic_arrow-flip_horizontal", historyReplyIconFg }}; + padding: margins(0px, 5px, -2px, 0px); +} msgBotKbIconPadding: 4px; msgBotKbUrlIcon: icon {{ "inline_button_url", msgBotKbIconFg }};