diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index e63a4651a..dac495e51 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -5360,10 +5360,26 @@ void ApiWrap::sendPollVotes( return; } const auto item = App::histItemById(itemId); + const auto media = item ? item->media() : nullptr; + const auto poll = media ? media->poll() : nullptr; if (!item) { return; } + const auto showSending = poll && !options.empty(); + const auto hideSending = [=] { + if (showSending) { + if (const auto item = App::histItemById(itemId)) { + poll->sendingVote = QByteArray(); + _session->data().requestItemRepaint(item); + } + } + }; + if (showSending) { + poll->sendingVote = options.front(); + _session->data().requestItemRepaint(item); + } + auto prepared = QVector(); prepared.reserve(options.size()); ranges::transform( @@ -5376,9 +5392,11 @@ void ApiWrap::sendPollVotes( MTP_vector(prepared) )).done([=](const MTPUpdates &result) { _pollVotesRequestIds.erase(itemId); + hideSending(); applyUpdates(result); }).fail([=](const RPCError &error) { _pollVotesRequestIds.erase(itemId); + hideSending(); }).send(); _pollVotesRequestIds.emplace(itemId, requestId); } diff --git a/Telegram/SourceFiles/data/data_poll.h b/Telegram/SourceFiles/data/data_poll.h index 5417d6343..8c0604324 100644 --- a/Telegram/SourceFiles/data/data_poll.h +++ b/Telegram/SourceFiles/data/data_poll.h @@ -39,6 +39,7 @@ struct PollData { std::vector answers; int totalVoters = 0; bool closed = false; + QByteArray sendingVote; int version = 0; diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 4eba6f5b4..0631eb5bb 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -525,6 +525,10 @@ historyPollRadio: Radio(defaultRadio) { historyPollRadioOpacity: 0.7; historyPollRadioOpacityOver: 1.; historyPollDuration: 300; +historyPollRadialAnimation: InfiniteRadialAnimation(defaultInfiniteRadialAnimation) { + thickness: 2px; + size: size(18px, 18px); +} boxAttachEmoji: IconButton(historyAttachEmoji) { width: 30px; diff --git a/Telegram/SourceFiles/history/media/history_media_poll.cpp b/Telegram/SourceFiles/history/media/history_media_poll.cpp index c5d6a75c2..1914ed22d 100644 --- a/Telegram/SourceFiles/history/media/history_media_poll.cpp +++ b/Telegram/SourceFiles/history/media/history_media_poll.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/history_view_cursor_state.h" #include "calls/calls_instance.h" #include "ui/text_options.h" +#include "ui/effects/radial_animation.h" #include "data/data_media_types.h" #include "data/data_poll.h" #include "data/data_session.h" @@ -61,6 +62,48 @@ FormattedLargeNumber FormatLargeNumber(int64 number) { } // namespace +struct HistoryPoll::AnswerAnimation { + anim::value percent; + anim::value filling; + anim::value opacity; +}; + +struct HistoryPoll::AnswersAnimation { + std::vector data; + Animation progress; +}; + +struct HistoryPoll::SendingAnimation { + SendingAnimation( + const QByteArray &option, + AnimationCallbacks &&callbacks); + + QByteArray option; + Ui::InfiniteRadialAnimation animation; +}; + +struct HistoryPoll::Answer { + Answer(); + + void fillText(const PollAnswer &original); + + 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; +}; + +HistoryPoll::SendingAnimation::SendingAnimation( + const QByteArray &option, + AnimationCallbacks &&callbacks) +: option(option) +, animation(std::move(callbacks), st::historyPollRadialAnimation) { +} + HistoryPoll::Answer::Answer() : text(st::msgMinWidth / 2) { } @@ -187,7 +230,7 @@ void HistoryPoll::updateTexts() { updateVotes(); if (willStartAnimation) { - startAnimation(); + startAnswersAnimation(); } } @@ -218,7 +261,7 @@ void HistoryPoll::updateAnswers() { answer.handler = createAnswerClickHandler(answer); } - _answersAnimation = nullptr; + resetAnswersAnimation(); } ClickHandlerPtr HistoryPoll::createAnswerClickHandler( @@ -236,12 +279,32 @@ void HistoryPoll::updateVotes() const { updateTotalVotes(); } -void HistoryPoll::updateVotesCheckAnimation() const { +void HistoryPoll::updateVotesCheckAnimations() const { const auto willStartAnimation = checkAnimationStart(); updateVotes(); if (willStartAnimation) { - startAnimation(); + startAnswersAnimation(); } + + const auto &sending = _poll->sendingVote; + if (sending.isEmpty() == !_sendingAnimation) { + if (_sendingAnimation) { + _sendingAnimation->option = sending; + } + return; + } + if (sending.isEmpty()) { + if (!_answersAnimation) { + _sendingAnimation = nullptr; + } + return; + } + _sendingAnimation = std::make_unique( + sending, + animation( + const_cast(this), + &HistoryPoll::step_radial)); + _sendingAnimation->animation.start(); } void HistoryPoll::updateTotalVotes() const { @@ -307,7 +370,7 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; auto paintx = 0, painty = 0, paintw = width(), painth = height(); - updateVotesCheckAnimation(); + updateVotesCheckAnimations(); const auto outbg = _parent->hasOutLayout(); const auto selected = (selection == FullSelection); @@ -332,7 +395,7 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time ? _answersAnimation->progress.current(ms, 1.) : 1.; if (progress == 1.) { - _answersAnimation = nullptr; + resetAnswersAnimation(); } auto &&answers = ranges::view::zip( @@ -366,6 +429,19 @@ void HistoryPoll::draw(Painter &p, const QRect &r, TextSelection selection, Time } } +void HistoryPoll::resetAnswersAnimation() const { + _answersAnimation = nullptr; + if (_poll->sendingVote.isEmpty()) { + _sendingAnimation = nullptr; + } +} + +void HistoryPoll::step_radial(TimeMs ms, bool timer) { + if (timer && !anim::Disabled()) { + Auth().data().requestViewRepaint(_parent); + } +} + int HistoryPoll::paintAnswer( Painter &p, const Answer &answer, @@ -457,13 +533,34 @@ void HistoryPoll::paintRadio( const auto &st = st::historyPollRadio; const auto over = ClickHandler::showAsActive(answer.handler); const auto ®ular = selected ? (outbg ? st::msgOutDateFgSelected : st::msgInDateFgSelected) : (outbg ? st::msgOutDateFg : st::msgInDateFg); - auto pen = regular->p; - pen.setWidth(st.thickness); - p.setPen(pen); + p.setBrush(Qt::NoBrush); const auto o = p.opacity(); p.setOpacity(o * (over ? st::historyPollRadioOpacityOver : st::historyPollRadioOpacity)); - p.drawEllipse(QRectF(left, top, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.))); + + const auto rect = QRectF(left, top, st.diameter, st.diameter).marginsRemoved(QMarginsF(st.thickness / 2., st.thickness / 2., st.thickness / 2., st.thickness / 2.)); + if (_sendingAnimation && _sendingAnimation->option == answer.option) { + const auto &active = selected ? (outbg ? st::msgOutServiceFgSelected : st::msgInServiceFgSelected) : (outbg ? st::msgOutServiceFg : st::msgInServiceFg); + if (anim::Disabled()) { + anim::DrawStaticLoading(p, rect, st.thickness, active); + } else { + const auto state = _sendingAnimation->animation.computeState(); + auto pen = anim::pen(regular, active, state.shown); + pen.setWidth(st.thickness); + pen.setCapStyle(Qt::RoundCap); + p.setPen(pen); + p.drawArc( + rect, + state.arcFrom, + state.arcLength); + } + } else { + auto pen = regular->p; + pen.setWidth(st.thickness); + p.setPen(pen); + p.drawEllipse(rect); + } + p.setOpacity(o); } @@ -564,7 +661,7 @@ bool HistoryPoll::checkAnimationStart() const { return result; } -void HistoryPoll::startAnimation() const { +void HistoryPoll::startAnswersAnimation() const { if (!_answersAnimation) { return; } @@ -587,7 +684,7 @@ void HistoryPoll::startAnimation() const { TextState HistoryPoll::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); - if (!canVote()) { + if (!canVote() || !_poll->sendingVote.isEmpty()) { return result; } diff --git a/Telegram/SourceFiles/history/media/history_media_poll.h b/Telegram/SourceFiles/history/media/history_media_poll.h index 4a5f69908..2b8a0dc4d 100644 --- a/Telegram/SourceFiles/history/media/history_media_poll.h +++ b/Telegram/SourceFiles/history/media/history_media_poll.h @@ -37,31 +37,10 @@ public: ~HistoryPoll(); private: - struct AnswerAnimation { - anim::value percent; - anim::value filling; - anim::value opacity; - }; - - struct AnswersAnimation { - std::vector data; - Animation progress; - }; - - struct Answer { - Answer(); - - void fillText(const PollAnswer &original); - - 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; - }; + struct AnswerAnimation; + struct AnswersAnimation; + struct SendingAnimation; + struct Answer; QSize countOptimalSize() override; QSize countCurrentSize(int newWidth) override; @@ -81,7 +60,7 @@ private: const PollAnswer &original, int totalVotes, int maxVotes) const; - void updateVotesCheckAnimation() const; + void updateVotesCheckAnimations() const; int paintAnswer( Painter &p, @@ -119,7 +98,9 @@ private: bool checkAnimationStart() const; bool answerVotesChanged() const; void saveStateInAnimation() const; - void startAnimation() const; + void startAnswersAnimation() const; + void resetAnswersAnimation() const; + void step_radial(TimeMs ms, bool timer); not_null _poll; int _pollVersion = 0; @@ -133,5 +114,6 @@ private: mutable Text _totalVotesLabel; mutable std::unique_ptr _answersAnimation; + mutable std::unique_ptr _sendingAnimation; };