From a91c2cd0a293e83d52ea57a3c440e0f8d7126c53 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 23 Mar 2026 15:58:02 +0300 Subject: [PATCH] [poll-view] Added countdown timer text to poll footer. --- Telegram/Resources/langs/lang.strings | 6 + .../history/view/media/history_view_poll.cpp | 232 ++++++++++-------- .../history/view/media/history_view_poll.h | 11 +- 3 files changed, 134 insertions(+), 115 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index a3e8307f25..88dff33ea9 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6842,6 +6842,12 @@ 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_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"; +"lng_polls_results_in_days#other" = "results in {count} days"; +"lng_polls_ends_in_time" = "ends in {time}"; +"lng_polls_results_in_time" = "results in {time}"; "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."; diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index 7cdd49ee05..ac134cad0a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -69,8 +69,6 @@ constexpr auto kRotateAmplitude = 3.; constexpr auto kScaleSegments = 2; constexpr auto kScaleAmplitude = 0.03; constexpr auto kRollDuration = crl::time(400); -constexpr auto kLargestRadialDuration = 30 * crl::time(1000); -constexpr auto kCriticalCloseDuration = 5 * crl::time(1000); [[nodiscard]] int PollAnswerMediaSize() { return st::historyPollRadio.diameter * 2; @@ -370,16 +368,6 @@ struct Poll::SolutionMedia { ClickHandlerPtr handler; }; -struct Poll::CloseInformation { - CloseInformation(TimeId date, TimeId period, Fn repaint); - - crl::time start = 0; - crl::time finish = 0; - crl::time duration = 0; - base::Timer timer; - Ui::Animations::Basic radial; -}; - struct Poll::RecentVoter { not_null peer; mutable Ui::PeerUserpicView userpic; @@ -443,16 +431,6 @@ void Poll::Answer::fillMedia( } } -Poll::CloseInformation::CloseInformation( - TimeId date, - TimeId period, - Fn repaint) -: duration(period * crl::time(1000)) -, timer(std::move(repaint)) { - const auto left = std::clamp(date - base::unixtime::now(), 0, period); - finish = crl::now() + left * crl::time(1000); -} - Poll::Poll( not_null parent, not_null poll, @@ -469,7 +447,8 @@ Poll::Poll( , _sendVotesLink( std::make_shared(crl::guard( this, - [=] { sendMultiOptions(); }))) { + [=] { sendMultiOptions(); }))) +, _closeTimer([=] { repaint(); }) { if (!consumed.text.isEmpty()) { updateDescription(); } @@ -512,6 +491,8 @@ QSize Poll::countOptimalSize() { const auto bottomButtonHeight = inlineFooter() ? 0 : st::historyPollBottomButtonSkip; + const auto timerMultiline = timerFooterMultiline( + maxWidth - paddings); auto minHeight = countQuestionTop(maxWidth - paddings, maxWidth) + _question.minHeight() + st::historyPollSubtitleSkip @@ -521,6 +502,7 @@ QSize Poll::countOptimalSize() { + st::historyPollTotalVotesSkip + bottomButtonHeight + st::msgDateFont->height + + (timerMultiline ? st::msgDateFont->height : 0) + st::msgPadding.bottom(); return { maxWidth, minHeight }; } @@ -614,6 +596,7 @@ QSize Poll::countCurrentSize(int newWidth) { const auto bottomButtonHeight = inlineFooter() ? 0 : st::historyPollBottomButtonSkip; + const auto timerMultiline = timerFooterMultiline(innerWidth); auto newHeight = countQuestionTop(innerWidth, newWidth) + _question.countHeight(innerWidth) + st::historyPollSubtitleSkip @@ -623,6 +606,7 @@ QSize Poll::countCurrentSize(int newWidth) { + st::historyPollTotalVotesSkip + bottomButtonHeight + st::msgDateFont->height + + (timerMultiline ? st::msgDateFont->height : 0) + st::msgPadding.bottom(); return { newWidth, newHeight }; } @@ -1560,7 +1544,6 @@ void Poll::draw(Painter &p, const PaintContext &context) const { p.setPen(stm->msgDateFg); _subtitle.drawLeftElided(p, padding.left(), tshift, paintw, width()); paintRecentVoters(p, padding.left() + _subtitle.maxWidth(), tshift, context); - paintCloseByTimer(p, padding.left() + paintw, tshift, context); paintShowSolution(p, padding.left() + paintw, tshift, context); tshift += st::msgDateFont->height + st::historyPollAnswersSkip; @@ -1612,14 +1595,45 @@ void Poll::paintInlineFooter( const PaintContext &context) const { const auto stm = context.messageStyle(); p.setPen(stm->msgDateFg); - const auto labelWidth = _totalVotesLabel.maxWidth(); - const auto labelLeft = left + (paintw - labelWidth) / 2; - _totalVotesLabel.drawLeftElided( - p, - labelLeft, - top, - labelWidth, - width()); + const auto timerText = closeTimerText(); + if (timerText.isEmpty()) { + const auto labelWidth = _totalVotesLabel.maxWidth(); + const auto labelLeft = left + (paintw - labelWidth) / 2; + _totalVotesLabel.drawLeftElided( + p, + labelLeft, + top, + labelWidth, + width()); + } else if (timerFooterMultiline(paintw)) { + const auto labelWidth = _totalVotesLabel.maxWidth(); + const auto labelLeft = left + (paintw - labelWidth) / 2; + _totalVotesLabel.drawLeftElided( + p, + labelLeft, + top, + labelWidth, + width()); + p.setFont(st::msgDateFont); + const auto timerw = st::msgDateFont->width(timerText); + p.drawTextLeft( + left + (paintw - timerw) / 2, + top + st::msgDateFont->height, + width(), + timerText, + timerw); + } else { + p.setFont(st::msgDateFont); + const auto sep = QString::fromUtf8(" \xC2\xB7 "); + const auto full = _totalVotesLabel.toString() + sep + timerText; + const auto fullw = st::msgDateFont->width(full); + p.drawTextLeft( + left + (paintw - fullw) / 2, + top, + width(), + full, + fullw); + } } void Poll::paintBottom( @@ -1634,14 +1648,45 @@ void Poll::paintBottom( const auto stm = context.messageStyle(); if (showVotersCount()) { p.setPen(stm->msgDateFg); - const auto labelWidth = _totalVotesLabel.maxWidth(); - const auto labelLeft = left + (paintw - labelWidth) / 2; - _totalVotesLabel.draw( - p, - labelLeft, - stringtop, - labelWidth, - style::al_top); + const auto timerText = closeTimerText(); + if (timerText.isEmpty()) { + const auto labelWidth = _totalVotesLabel.maxWidth(); + const auto labelLeft = left + (paintw - labelWidth) / 2; + _totalVotesLabel.draw( + p, + labelLeft, + stringtop, + labelWidth, + style::al_top); + } else if (timerFooterMultiline(paintw)) { + const auto labelWidth = _totalVotesLabel.maxWidth(); + const auto labelLeft = left + (paintw - labelWidth) / 2; + _totalVotesLabel.draw( + p, + labelLeft, + stringtop, + labelWidth, + style::al_top); + p.setFont(st::msgDateFont); + const auto timerw = st::msgDateFont->width(timerText); + p.drawTextLeft( + left + (paintw - timerw) / 2, + stringtop + st::msgDateFont->height, + width(), + timerText, + timerw); + } else { + p.setFont(st::msgDateFont); + const auto sep = QString::fromUtf8(" \xC2\xB7 "); + const auto full = _totalVotesLabel.toString() + sep + timerText; + const auto fullw = st::msgDateFont->width(full); + p.drawTextLeft( + left + (paintw - fullw) / 2, + stringtop, + width(), + full, + fullw); + } } else { const auto link = showVotes() ? _showResultsLink @@ -1743,74 +1788,6 @@ void Poll::paintRecentVoters( } } -void Poll::paintCloseByTimer( - Painter &p, - int right, - int top, - const PaintContext &context) const { - if (!canVote() || _poll->closeDate <= 0 || _poll->closePeriod <= 0) { - _close = nullptr; - return; - } - if (!_close) { - _close = std::make_unique( - _poll->closeDate, - _poll->closePeriod, - [=] { repaint(); }); - } - const auto now = crl::now(); - const auto left = std::max(_close->finish - now, crl::time(0)); - const auto radial = std::min(_close->duration, kLargestRadialDuration); - if (!left) { - _close->radial.stop(); - } else if (left < radial && !anim::Disabled()) { - if (!_close->radial.animating()) { - _close->radial.init([=] { - repaint(); - }); - _close->radial.start(); - } - } else { - _close->radial.stop(); - } - const auto time = Ui::FormatDurationText(int(std::ceil(left / 1000.))); - const auto st = context.st; - const auto stm = context.messageStyle(); - const auto &icon = stm->historyQuizTimer; - const auto x = right - icon.width(); - const auto y = top - + (st::normalFont->height - icon.height()) / 2 - - st::lineWidth; - const auto ®ular = (left < kCriticalCloseDuration) - ? st->boxTextFgError() - : stm->msgDateFg; - p.setPen(regular); - const auto timeWidth = st::normalFont->width(time); - p.drawTextLeft(x - timeWidth, top, width(), time, timeWidth); - if (left < radial) { - auto hq = PainterHighQualityEnabler(p); - const auto part = std::max( - left / float64(radial), - 1. / arc::kFullLength); - const auto length = int(base::SafeRound(arc::kFullLength * part)); - auto pen = regular->p; - pen.setWidth(st::historyPollRadio.thickness); - pen.setCapStyle(Qt::RoundCap); - p.setPen(pen); - const auto size = icon.width() / 2; - const auto left = (x + (icon.width() - size) / 2); - const auto top = (y + (icon.height() - size) / 2) + st::lineWidth; - p.drawArc(left, top, size, size, (arc::kFullLength / 4), length); - } else { - icon.paint(p, x, y, width()); - } - - if (left > (anim::Disabled() ? 0 : (radial - 1))) { - const auto next = (left % 1000); - _close->timer.callOnce((next ? next : 1000) + 1); - } -} - void Poll::paintShowSolution( Painter &p, int right, @@ -2890,6 +2867,45 @@ int Poll::bottomButtonHeight() const { + st::msgPadding.bottom(); } +QString Poll::closeTimerText() const { + if (_poll->closeDate <= 0 || (_flags & PollData::Flag::Closed)) { + _closeTimer.cancel(); + return {}; + } + const auto left = _poll->closeDate - base::unixtime::now(); + if (left <= 0) { + _closeTimer.cancel(); + return {}; + } + if (!_closeTimer.isActive()) { + _closeTimer.callEach(1000); + } + const auto hideResults = (_flags + & PollData::Flag::HideResultsUntilClose); + if (left >= 86400) { + const auto days = (left + 86399) / 86400; + return hideResults + ? tr::lng_polls_results_in_days(tr::now, lt_count, days) + : tr::lng_polls_ends_in_days(tr::now, lt_count, days); + } + const auto timer = Ui::FormatDurationText(left); + return hideResults + ? tr::lng_polls_results_in_time(tr::now, lt_time, timer) + : tr::lng_polls_ends_in_time(tr::now, lt_time, timer); +} + +bool Poll::timerFooterMultiline(int paintw) const { + const auto timerText = closeTimerText(); + if (timerText.isEmpty()) { + return false; + } + const auto sep = QString::fromUtf8(" \xC2\xB7 "); + const auto full = _totalVotesLabel.toString() + sep + timerText; + const auto fullw = st::msgDateFont->width(full); + const auto skipw = _parent->skipBlockWidth(); + return (paintw + fullw) / 2 > paintw - skipw; +} + void Poll::toggleLinkRipple(bool pressed) { if (pressed) { const auto linkWidth = width(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.h b/Telegram/SourceFiles/history/view/media/history_view_poll.h index ff1620f124..688aaefd7b 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.h +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.h @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "data/data_poll.h" #include "base/weak_ptr.h" +#include "base/timer.h" namespace Ui { class RippleAnimation; @@ -77,7 +78,6 @@ private: struct Answer; struct AttachedMedia; struct SolutionMedia; - struct CloseInformation; struct RecentVoter; QSize countOptimalSize() override; @@ -139,11 +139,6 @@ private: int left, int top, const PaintContext &context) const; - void paintCloseByTimer( - Painter &p, - int right, - int top, - const PaintContext &context) const; void paintShowSolution( Painter &p, int right, @@ -234,6 +229,8 @@ private: const PaintContext &context) const; [[nodiscard]] int bottomButtonHeight() const; + [[nodiscard]] QString closeTimerText() const; + [[nodiscard]] bool timerFooterMultiline(int paintw) const; const not_null _poll; int _pollVersion = 0; @@ -267,7 +264,7 @@ private: mutable QImage _userpicCircleCache; mutable QImage _fillingIconCache; - mutable std::unique_ptr _close; + mutable base::Timer _closeTimer; mutable Ui::Animations::Simple _solutionButtonAnimation; mutable bool _solutionShown = false;