diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index c22f43ca7..8839d8bbc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -853,6 +853,8 @@ PRIVATE support/support_helper.h support/support_templates.cpp support/support_templates.h + ui/effects/fireworks_animation.cpp + ui/effects/fireworks_animation.h ui/effects/round_checkbox.cpp ui/effects/round_checkbox.h ui/effects/send_action_animations.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 34a13bc57..7987616af 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2204,6 +2204,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_create_multiple_choice" = "Multiple Answers"; "lng_polls_create_quiz_mode" = "Quiz Mode"; "lng_polls_create_button" = "Create"; +"lng_polls_choose_question" = "Please enter a question."; +"lng_polls_choose_answers" = "Please enter at least two options."; +"lng_polls_choose_correct" = "Please choose the correct answer."; "lng_polls_poll_results_title" = "Poll results"; "lng_polls_quiz_results_title" = "Quiz results"; diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index a281d969f..6f4ffc088 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.9.8.0" /> Telegram Desktop Telegram FZ-LLC diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 1d9b433bf..4b37d38c6 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -48,8 +48,9 @@ public: not_null session, bool chooseCorrectEnabled); + [[nodiscard]] bool hasOptions() const; [[nodiscard]] bool isValid() const; - [[nodiscard]] rpl::producer isValidChanged() const; + [[nodiscard]] bool hasCorrect() const; [[nodiscard]] std::vector toPollAnswers() const; void focusFirst(); @@ -139,8 +140,10 @@ private: int _position = 0; std::vector> _list; std::vector> _destroyed; - rpl::variable _valid = false; rpl::variable _usedCount = 0; + bool _hasOptions = false; + bool _isValid = false; + bool _hasCorrect = false; rpl::event_stream> _scrollToWidget; rpl::event_stream<> _backspaceInFront; @@ -470,12 +473,16 @@ bool Options::full() const { return (_list.size() == kMaxOptionsCount); } -bool Options::isValid() const { - return _valid.current(); +bool Options::hasOptions() const { + return _hasOptions; } -rpl::producer Options::isValidChanged() const { - return _valid.changes(); +bool Options::isValid() const { + return _isValid; +} + +bool Options::hasCorrect() const { + return _hasCorrect; } rpl::producer Options::usedCount() const { @@ -539,10 +546,10 @@ void Options::enableChooseCorrect(bool enabled) { _chooseCorrectGroup = enabled ? createChooseCorrectGroup() : nullptr; - validateState(); for (auto &option : _list) { option->enableChooseCorrect(_chooseCorrectGroup); } + validateState(); } bool Options::correctShadows() const { @@ -696,10 +703,11 @@ void Options::removeDestroyed(not_null option) { void Options::validateState() { checkLastOption(); - _valid = (ranges::count_if(_list, &Option::isGood) > 1) - && (ranges::find_if(_list, &Option::isTooLong) == end(_list)) - && (!_chooseCorrectGroup - || ranges::find_if(_list, &Option::isCorrect) != end(_list)); + _hasOptions = (ranges::count_if(_list, &Option::isGood) > 1); + _isValid = _hasOptions + && (ranges::find_if(_list, &Option::isTooLong) == end(_list)); + _hasCorrect = ranges::find_if(_list, &Option::isCorrect) != end(_list); + const auto lastEmpty = !_list.empty() && _list.back()->isEmpty(); _usedCount = _list.size() - (lastEmpty ? 1 : 0); } @@ -789,7 +797,7 @@ object_ptr CreatePollBox::setupContent() { using namespace Settings; const auto id = rand_value(); - const auto valid = lifetime().make_state>(); + const auto error = lifetime().make_state(Error::Question); auto result = object_ptr(this); const auto container = result.data(); @@ -910,8 +918,41 @@ object_ptr CreatePollBox::setupContent() { | (quiz->checked() ? Flag::Quiz : Flag(0))); return result; }; - const auto send = [=](Api::SendOptions options) { - _submitRequests.fire({ collectResult(), options }); + const auto collectError = [=] { + if (isValidQuestion()) { + *error &= ~Error::Question; + } else { + *error |= Error::Question; + } + if (!options->hasOptions()) { + *error |= Error::Options; + } else if (!options->isValid()) { + *error |= Error::Other; + } else { + *error &= ~(Error::Options | Error::Other); + } + if (quiz->checked() && !options->hasCorrect()) { + *error |= Error::Correct; + } else { + *error &= ~Error::Correct; + } + }; + const auto showError = [=](const QString &text) { + Ui::Toast::Show(text); + }; + const auto send = [=](Api::SendOptions sendOptions) { + collectError(); + if (*error & Error::Question) { + showError(tr::lng_polls_choose_question(tr::now)); + question->setFocus(); + } else if (*error & Error::Options) { + showError(tr::lng_polls_choose_answers(tr::now)); + options->focusFirst(); + } else if (*error & Error::Correct) { + showError(tr::lng_polls_choose_correct(tr::now)); + } else if (!*error) { + _submitRequests.fire({ collectResult(), sendOptions }); + } }; const auto sendSilent = [=] { auto options = Api::SendOptions(); @@ -926,36 +967,6 @@ object_ptr CreatePollBox::setupContent() { send), Ui::LayerOption::KeepOther); }; - const auto updateValid = [=] { - valid->fire(isValidQuestion() && options->isValid()); - }; - connect(question, &Ui::InputField::changed, [=] { - updateValid(); - }); - valid->events_starting_with( - false - ) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=](bool valid) { - clearButtons(); - if (valid) { - const auto submit = addButton( - tr::lng_polls_create_button(), - [=] { send({}); }); - if (_sendType == Api::SendType::Normal) { - SetupSendMenuAndShortcuts( - submit.data(), - [=] { return SendMenuType::Scheduled; }, - sendSilent, - sendScheduled); - } - } - addButton(tr::lng_cancel(), [=] { closeBox(); }); - }, lifetime()); - - options->isValidChanged( - ) | rpl::start_with_next([=] { - updateValid(); - }, lifetime()); options->scrollToWidget( ) | rpl::start_with_next([=](not_null widget) { @@ -967,6 +978,22 @@ object_ptr CreatePollBox::setupContent() { FocusAtEnd(question); }, lifetime()); + const auto submit = addButton( + tr::lng_polls_create_button(), + [=] { send({}); }); + if (_sendType == Api::SendType::Normal) { + const auto sendMenuType = [=] { + collectError(); + return *error ? SendMenuType::Disabled : SendMenuType::Scheduled; + }; + SetupSendMenuAndShortcuts( + submit.data(), + sendMenuType, + sendSilent, + sendScheduled); + } + addButton(tr::lng_cancel(), [=] { closeBox(); }); + return result; } diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h index e1c5c0760..f05b1dce1 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.h +++ b/Telegram/SourceFiles/boxes/create_poll_box.h @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/abstract_box.h" #include "api/api_common.h" #include "data/data_poll.h" +#include "base/flags.h" struct PollData; @@ -44,6 +45,15 @@ protected: void prepare() override; private: + enum class Error { + Question = 0x01, + Options = 0x02, + Correct = 0x04, + Other = 0x08, + }; + friend constexpr inline bool is_flag_type(Error) { return true; } + using Errors = base::flags; + object_ptr setupContent(); not_null setupQuestion( not_null container); diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index 881fc1c25..f93f1be5e 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -15,8 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #define TDESKTOP_ALPHA_VERSION (0ULL) #endif // TDESKTOP_ALLOW_CLOSED_ALPHA -constexpr auto AppVersion = 1009007; -constexpr auto AppVersionStr = "1.9.7"; +constexpr auto AppVersion = 1009008; +constexpr auto AppVersionStr = "1.9.8"; constexpr auto AppBetaVersion = false; constexpr auto AppAlphaVersion = TDESKTOP_ALPHA_VERSION; constexpr auto AppKotatoVersion = 1001004; diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index f16a11840..977d614bb 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -168,13 +168,12 @@ bool PollData::applyResultToAnswers( answer->chosen = voters.is_chosen(); changed = true; } + } + if (!isMinResults || closed()) { if (answer->correct != voters.is_correct()) { answer->correct = voters.is_correct(); changed = true; } - } else if (const auto existing = answerByOption(option)) { - answer->chosen = existing->chosen; - answer->correct = existing->correct; } return changed; }); diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp index 7e84e1748..bb4dd9320 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_inner.cpp @@ -1707,7 +1707,9 @@ void InnerWidget::repaintItem(const Element *view) { if (!view) { return; } - update(0, itemTop(view), width(), view->height()); + const auto top = itemTop(view); + const auto range = view->verticalRepaintRange(); + update(0, top + range.top, width(), range.height); } void InnerWidget::resizeItem(not_null view) { diff --git a/Telegram/SourceFiles/history/history.style b/Telegram/SourceFiles/history/history.style index 279274f61..61657eb39 100644 --- a/Telegram/SourceFiles/history/history.style +++ b/Telegram/SourceFiles/history/history.style @@ -542,10 +542,10 @@ historyPollQuestionStyle: TextStyle(defaultTextStyle) { historyPollAnswerStyle: defaultTextStyle; historyPollQuestionTop: 7px; historyPollSubtitleSkip: 4px; -historyPollAnswerPadding: margins(31px, 10px, 0px, 10px); +historyPollAnswerPadding: margins(32px, 10px, 0px, 10px); historyPollAnswersSkip: 2px; historyPollPercentFont: semiboldFont; -historyPollPercentSkip: 6px; +historyPollPercentSkip: 5px; historyPollPercentTop: 0px; historyPollTotalVotesSkip: 5px; historyPollFillingMin: 4px; diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 7dba4fa53..1a012f462 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -242,7 +242,8 @@ void HistoryInner::repaintItem(const Element *view) { } const auto top = itemTop(view); if (top >= 0) { - update(0, top, width(), view->height()); + const auto range = view->verticalRepaintRange(); + update(0, top + range.top, width(), range.height); } } diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index 1cb390ae8..d734092fe 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -605,6 +605,13 @@ bool Element::hasVisibleText() const { return false; } +auto Element::verticalRepaintRange() const -> VerticalRepaintRange { + return { + .top = 0, + .height = height() + }; +} + void Element::unloadHeavyPart() { if (_media) { _media->unloadHeavyPart(); diff --git a/Telegram/SourceFiles/history/view/history_view_element.h b/Telegram/SourceFiles/history/view/history_view_element.h index 235ef1d06..cc1b75ddf 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.h +++ b/Telegram/SourceFiles/history/view/history_view_element.h @@ -264,6 +264,12 @@ public: virtual TimeId displayedEditDate() const; virtual bool hasVisibleText() const; + struct VerticalRepaintRange { + int top = 0; + int height = 0; + }; + [[nodiscard]] virtual VerticalRepaintRange verticalRepaintRange() const; + virtual void unloadHeavyPart(); // Legacy blocks structure. diff --git a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp index ba34f6f12..db814c798 100644 --- a/Telegram/SourceFiles/history/view/history_view_list_widget.cpp +++ b/Telegram/SourceFiles/history/view/history_view_list_widget.cpp @@ -2398,7 +2398,9 @@ void ListWidget::repaintItem(const Element *view) { if (!view) { return; } - update(0, itemTop(view), width(), view->height()); + const auto top = itemTop(view); + const auto range = view->verticalRepaintRange(); + update(0, top + range.top, width(), range.height); } void ListWidget::repaintItem(FullMsgId itemId) { diff --git a/Telegram/SourceFiles/history/view/history_view_message.cpp b/Telegram/SourceFiles/history/view/history_view_message.cpp index 52fc7693c..2bf12003f 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.cpp +++ b/Telegram/SourceFiles/history/view/history_view_message.cpp @@ -409,6 +409,15 @@ void Message::draw( paintHighlight(p, g.height()); + const auto roll = media ? media->bubbleRoll() : Media::BubbleRoll(); + if (roll) { + p.save(); + p.translate(g.center()); + p.rotate(roll.rotate); + p.scale(roll.scale, roll.scale); + p.translate(-g.center()); + } + p.setTextPalette(selected ? (outbg ? st::outTextPaletteSelected : st::inTextPaletteSelected) : (outbg ? st::outTextPalette : st::inTextPalette)); @@ -496,6 +505,10 @@ void Message::draw( const auto fastShareTop = g.top() + g.height() - fastShareSkip - st::historyFastShareSize; drawRightAction(p, fastShareLeft, fastShareTop, width()); } + + if (media) { + media->paintBubbleFireworks(p, g, ms); + } } else if (media && media->isDisplayed()) { p.translate(g.topLeft()); media->draw(p, clip.translated(-g.topLeft()), skipTextSelection(selection), ms); @@ -504,6 +517,10 @@ void Message::draw( p.restoreTextPalette(); + if (roll) { + p.restore(); + } + const auto reply = item->Get(); if (reply && reply->isNameUpdated()) { const_cast(this)->setPendingResize(); @@ -1280,6 +1297,15 @@ int Message::infoWidth() const { return result; } +auto Message::verticalRepaintRange() const -> VerticalRepaintRange { + const auto media = this->media(); + const auto add = media ? media->bubbleRollRepaintMargins() : QMargins(); + return { + .top = -add.top(), + .height = height() + add.top() + add.bottom() + }; +} + void Message::refreshDataIdHook() { if (base::take(_rightActionLink)) { _rightActionLink = rightActionLink(); diff --git a/Telegram/SourceFiles/history/view/history_view_message.h b/Telegram/SourceFiles/history/view/history_view_message.h index 05479137e..aa81002ce 100644 --- a/Telegram/SourceFiles/history/view/history_view_message.h +++ b/Telegram/SourceFiles/history/view/history_view_message.h @@ -87,6 +87,8 @@ public: int infoWidth() const override; int plainMaxWidth() const override; + VerticalRepaintRange verticalRepaintRange() const override; + protected: void refreshDataIdHook() override; diff --git a/Telegram/SourceFiles/history/view/media/history_view_media.h b/Telegram/SourceFiles/history/view/media/history_view_media.h index 96335abae..0708ca2a0 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media.h @@ -233,6 +233,26 @@ public: return true; } + struct BubbleRoll { + float64 rotate = 0.; + float64 scale = 1.; + + explicit operator bool() const { + return (rotate != 0.) || (scale != 1.); + } + }; + [[nodiscard]] virtual BubbleRoll bubbleRoll() const { + return BubbleRoll(); + } + [[nodiscard]] virtual QMargins bubbleRollRepaintMargins() const { + return QMargins(); + } + virtual void paintBubbleFireworks( + Painter &p, + const QRect &bubble, + crl::time ms) const { + } + virtual void unloadHeavyPart() { } diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp index a355b457a..9bfc98ae5 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/effects/animations.h" #include "ui/effects/radial_animation.h" #include "ui/effects/ripple_animation.h" +#include "ui/effects/fireworks_animation.h" #include "data/data_media_types.h" #include "data/data_poll.h" #include "data/data_user.h" @@ -32,6 +33,11 @@ namespace HistoryView { namespace { constexpr auto kShowRecentVotersCount = 3; +constexpr auto kRotateSegments = 8; +constexpr auto kRotateAmplitude = 3.; +constexpr auto kScaleSegments = 2; +constexpr auto kScaleAmplitude = 0.03; +constexpr auto kRollDuration = crl::time(400); struct PercentCounterItem { int index = 0; @@ -344,6 +350,7 @@ void Poll::updateTexts() { _pollVersion = _poll->version; const auto willStartAnimation = checkAnimationStart(); + const auto voted = _voted; if (_question.toString() != _poll->question) { auto options = Ui::WebpageTextTitleOptions(); @@ -374,6 +381,30 @@ void Poll::updateTexts() { if (willStartAnimation) { startAnswersAnimation(); + if (!voted) { + checkQuizAnswered(); + } + } +} + +void Poll::checkQuizAnswered() { + if (!_voted || !_votedFromHere || !_poll->quiz() || anim::Disabled()) { + return; + } + const auto i = ranges::find(_answers, true, &Answer::chosen); + if (i == end(_answers)) { + return; + } + if (i->correct) { + _fireworksAnimation = std::make_unique( + [=] { history()->owner().requestViewRepaint(_parent); }); + } else { + _wrongAnswerAnimation.start( + [=] { history()->owner().requestViewRepaint(_parent); }, + 0., + 1., + kRollDuration, + anim::linear); } } @@ -426,6 +457,7 @@ ClickHandlerPtr Poll::createAnswerClickHandler( })); } return std::make_shared(crl::guard(this, [=] { + _votedFromHere = true; history()->session().api().sendPollVotes( _parent->data()->fullId(), { option }); @@ -465,9 +497,7 @@ void Poll::sendMultiOptions() { &Answer::option ) | ranges::to_vector; if (!chosen.empty()) { - for (auto &answer : _answers) { - answer.selected = false; - } + _votedFromHere = true; history()->session().api().sendPollVotes( _parent->data()->fullId(), std::move(chosen)); @@ -481,7 +511,17 @@ void Poll::showResults() { } void Poll::updateVotes() { - _voted = _poll->voted(); + const auto voted = _poll->voted(); + if (_voted != voted) { + _voted = voted; + if (_voted) { + for (auto &answer : _answers) { + answer.selected = false; + } + } else { + _votedFromHere = false; + } + } updateAnswerVotes(); updateTotalVotes(); } @@ -961,6 +1001,8 @@ void Poll::paintFilling( if (chosen && !correct) { p.setBrush(st::boxTextFgError); + } else if (chosen && correct && _poll->quiz() && !outbg) { + p.setBrush(st::boxTextFgGood); } else { const auto bar = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); p.setBrush(bar); @@ -1119,6 +1161,49 @@ TextState Poll::textState(QPoint point, StateRequest request) const { return result; } +auto Poll::bubbleRoll() const -> BubbleRoll { + const auto value = _wrongAnswerAnimation.value(1.); + _wrongAnswerAnimated = (value < 1.); + if (!_wrongAnswerAnimated) { + return BubbleRoll(); + } + const auto rotateFull = value * kRotateSegments; + const auto progress = [](float64 full) { + const auto lower = std::floor(full); + const auto shift = (full - lower); + switch (int(lower) % 4) { + case 0: return -shift; + case 1: return (shift - 1.); + case 2: return shift; + case 3: return (1. - shift); + } + Unexpected("Value in Poll::getBubbleRollDegrees."); + }; + return { + .rotate = progress(value * kRotateSegments) * kRotateAmplitude, + .scale = 1. + progress(value * kScaleSegments) * kScaleAmplitude + }; +} + +QMargins Poll::bubbleRollRepaintMargins() const { + if (!_wrongAnswerAnimated) { + return QMargins(); + } + static const auto kAdd = int(std::ceil( + st::msgMaxWidth * std::sin(kRotateAmplitude * M_PI / 180.))); + return QMargins(kAdd, kAdd, kAdd, kAdd); +} + +void Poll::paintBubbleFireworks( + Painter &p, + const QRect &bubble, + crl::time ms) const { + if (!_fireworksAnimation || _fireworksAnimation->paint(p, bubble)) { + return; + } + _fireworksAnimation = nullptr; +} + void Poll::clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) { diff --git a/Telegram/SourceFiles/history/view/media/history_view_poll.h b/Telegram/SourceFiles/history/view/media/history_view_poll.h index 4fc57b111..b689fa96a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_poll.h +++ b/Telegram/SourceFiles/history/view/media/history_view_poll.h @@ -8,11 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "history/view/media/history_view_media.h" +#include "ui/effects/animations.h" #include "data/data_poll.h" #include "base/weak_ptr.h" namespace Ui { class RippleAnimation; +class FireworksAnimation; } // namespace Ui namespace HistoryView { @@ -40,6 +42,13 @@ public: return false; } + BubbleRoll bubbleRoll() const override; + QMargins bubbleRollRepaintMargins() const override; + void paintBubbleFireworks( + Painter &p, + const QRect &bubble, + crl::time ms) const override; + void clickHandlerPressedChanged( const ClickHandlerPtr &handler, bool pressed) override; @@ -145,6 +154,7 @@ private: void toggleMultiOption(const QByteArray &option); void sendMultiOptions(); void showResults(); + void checkQuizAnswered(); [[nodiscard]] int bottomButtonHeight() const; @@ -164,12 +174,17 @@ private: ClickHandlerPtr _showResultsLink; ClickHandlerPtr _sendVotesLink; mutable std::unique_ptr _linkRipple; - bool _hasSelected = false; mutable std::unique_ptr _answersAnimation; mutable std::unique_ptr _sendingAnimation; + mutable std::unique_ptr _fireworksAnimation; + Ui::Animations::Simple _wrongAnswerAnimation; mutable QPoint _lastLinkPoint; + bool _hasSelected = false; + bool _votedFromHere = false; + mutable bool _wrongAnswerAnimated = false; + }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp index cfd59770f..5fff698f3 100644 --- a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.cpp @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "platform/linux/linux_desktop_environment.h" +#include "platform/linux/specific_linux.h" + #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION #include #endif @@ -26,7 +28,14 @@ Type Compute() { auto xdgCurrentDesktop = GetEnv("XDG_CURRENT_DESKTOP").toLower(); auto list = xdgCurrentDesktop.split(':', QString::SkipEmptyParts); auto desktopSession = GetEnv("DESKTOP_SESSION").toLower(); + auto slash = desktopSession.lastIndexOf('/'); auto kdeSession = GetEnv("KDE_SESSION_VERSION"); + + // DESKTOP_SESSION can contain a path + if (slash != -1) { + desktopSession = desktopSession.mid(slash + 1); + } + if (!list.isEmpty()) { if (list.contains("unity")) { // gnome-fallback sessions set XDG_CURRENT_DESKTOP to Unity @@ -35,8 +44,6 @@ Type Compute() { return Type::Gnome; } return Type::Unity; - } else if (list.contains("xfce")) { - return Type::XFCE; } else if (list.contains("pantheon")) { return Type::Pantheon; } else if (list.contains("gnome")) { @@ -53,7 +60,7 @@ Type Compute() { } if (!desktopSession.isEmpty()) { - if (desktopSession == qstr("gnome") || desktopSession == qstr("mate")) { + if (desktopSession == qstr("gnome")) { return Type::Gnome; } else if (desktopSession == qstr("kde4") || desktopSession == qstr("kde-plasma")) { return Type::KDE4; @@ -63,10 +70,6 @@ Type Compute() { return Type::KDE4; } return Type::KDE3; - } else if (desktopSession.indexOf(qstr("xfce")) >= 0 || desktopSession == qstr("xubuntu")) { - return Type::XFCE; - } else if (desktopSession == qstr("awesome")) { - return Type::Awesome; } } @@ -95,9 +98,7 @@ Type ComputeAndLog() { case Type::KDE5: return "KDE5"; case Type::Ubuntu: return "Ubuntu"; case Type::Unity: return "Unity"; - case Type::XFCE: return "XFCE"; case Type::Pantheon: return "Pantheon"; - case Type::Awesome: return "Awesome"; } return QString::number(static_cast(result)); }; @@ -114,15 +115,17 @@ Type Get() { } bool TryQtTrayIcon() { - return !IsPantheon() && !IsAwesome(); + return !IsPantheon(); } bool PreferAppIndicatorTrayIcon() { - return IsXFCE() || IsUnity() || IsUbuntu() || + return (InSandbox() && !IsKDE()) + || IsUnity() + || IsUbuntu() #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION - (IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid()); + || (IsGnome() && QDBusInterface("org.kde.StatusNotifierWatcher", "/").isValid()); #else - IsGnome(); + || IsGnome(); #endif } diff --git a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h index e8291b240..129b9ef9f 100644 --- a/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h +++ b/Telegram/SourceFiles/platform/linux/linux_desktop_environment.h @@ -18,9 +18,7 @@ enum class Type { KDE5, Ubuntu, Unity, - XFCE, Pantheon, - Awesome, }; Type Get(); @@ -53,18 +51,10 @@ inline bool IsUnity() { return Get() == Type::Unity; } -inline bool IsXFCE() { - return Get() == Type::XFCE; -} - inline bool IsPantheon() { return Get() == Type::Pantheon; } -inline bool IsAwesome() { - return Get() == Type::Awesome; -} - bool TryQtTrayIcon(); bool PreferAppIndicatorTrayIcon(); diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 505bfaec4..794b205ef 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -11,9 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "facades.h" -#include - #ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION +#include #include #include #include @@ -101,7 +100,8 @@ NotificationData::NotificationData( if (capabilities.contains(qsl("body-markup"))) { _body = subtitle.isEmpty() ? msg.toHtmlEscaped() - : qsl("%1\n%2").arg(subtitle.toHtmlEscaped()) + : qsl("%1\n%2") + .arg(subtitle.toHtmlEscaped()) .arg(msg.toHtmlEscaped()); } else { _body = subtitle.isEmpty() @@ -351,7 +351,7 @@ void Manager::Private::showNotification( const auto key = hideNameAndPhoto ? InMemoryKey() - :peer->userpicUniqueKey(); + : peer->userpicUniqueKey(); notification->setImage(_cachedUserpics.get(key, peer)); auto i = _notifications.find(peer->id); diff --git a/Telegram/SourceFiles/platform/linux/specific_linux.cpp b/Telegram/SourceFiles/platform/linux/specific_linux.cpp index 26dcc5198..536f24d43 100644 --- a/Telegram/SourceFiles/platform/linux/specific_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/specific_linux.cpp @@ -384,6 +384,7 @@ bool OpenSystemSettings(SystemSettingsType type) { } else if (DesktopEnvironment::IsGnome()) { add("gnome-control-center sound"); } + add("pavucontrol-qt"); add("pavucontrol"); add("alsamixergui"); return ranges::find_if(options, [](const QString &command) { diff --git a/Telegram/SourceFiles/ui/effects/fireworks_animation.cpp b/Telegram/SourceFiles/ui/effects/fireworks_animation.cpp new file mode 100644 index 000000000..2a6bb7f35 --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/fireworks_animation.cpp @@ -0,0 +1,221 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "ui/effects/fireworks_animation.h" + +#include "base/openssl_help.h" + +namespace Ui { +namespace { + +constexpr auto kParticlesCount = 60; +constexpr auto kFallCount = 30; +constexpr auto kFirstUpdateTime = crl::time(16); +constexpr auto kFireworkWidth = 480; +constexpr auto kFireworkHeight = 320; + +QBrush Brush(int color) { + return QBrush{ QColor( + color & 0xFF, + (color >> 8) & 0xFF, + (color >> 16) & 0xFF) + }; +} + +std::vector PrepareBrushes() { + return { + Brush(0xff2CBCE8), + Brush(0xff9E04D0), + Brush(0xffFECB02), + Brush(0xffFD2357), + Brush(0xff278CFE), + Brush(0xff59B86C), + }; +} + +int RandomInt(uint32 till) { + return int(openssl::RandomValue() % till); +} + +[[nodiscard]] float64 RandomFloat01() { + return openssl::RandomValue() + / float64(std::numeric_limits::max()); +} + +} // namespace + +FireworksAnimation::FireworksAnimation(Fn repaint) +: _brushes(PrepareBrushes()) +, _animation([=](crl::time now) { update(now); }) +, _repaint(std::move(repaint)) { + _smallSide = style::ConvertScale(2); + _particles.reserve(kParticlesCount + kFallCount); + for (auto i = 0; i != kParticlesCount; ++i) { + initParticle(_particles.emplace_back(), false); + } + _animation.start(); +} + +void FireworksAnimation::update(crl::time now) { + const auto passed = _lastUpdate ? (now - _lastUpdate) : kFirstUpdateTime; + _lastUpdate = now; + auto allFinished = true; + for (auto &particle : _particles) { + updateParticle(particle, passed); + if (!particle.finished) { + allFinished = false; + } + } + if (allFinished) { + _animation.stop(); + } else if (_fallingDown >= kParticlesCount / 2 && _speedCoef > 0.2) { + startFall(); + _speedCoef -= passed / 16.0 * 0.15; + if (_speedCoef < 0.2) { + _speedCoef = 0.2; + } + } + _repaint(); +} + +bool FireworksAnimation::paint(QPainter &p, const QRect &rect) { + if (rect.isEmpty()) { + return false; + } + PainterHighQualityEnabler hq(p); + p.setPen(Qt::NoPen); + p.setClipRect(rect); + for (auto &particle : _particles) { + if (!particle.finished) { + paintParticle(p, particle, rect); + } + } + p.setClipping(false); + return _animation.animating(); +} + +void FireworksAnimation::paintParticle( + QPainter &p, + const Particle &particle, + const QRect &rect) { + const auto size = particle.size; + const auto x = rect.x() + (particle.x * rect.width() / kFireworkWidth); + const auto y = rect.y() + (particle.y * rect.height() / kFireworkHeight); + p.setBrush(_brushes[particle.color]); + if (particle.type == Particle::Type::Circle) { + p.drawEllipse(x, y, size, size); + } else { + const auto rect = QRect(-size, -_smallSide, size, _smallSide); + p.save(); + p.translate(x, y); + p.rotate(particle.rotation); + p.drawRoundedRect(rect, _smallSide, _smallSide); + p.restore(); + } +} + +void FireworksAnimation::updateParticle(Particle &particle, crl::time dt) { + if (particle.finished) { + return; + } + const auto moveCoef = dt / 16.; + particle.x += particle.moveX * moveCoef; + particle.y += particle.moveY * moveCoef; + if (particle.xFinished != 0) { + const auto dp = 0.5; + if (particle.xFinished == 1) { + particle.moveX += dp * moveCoef * 0.05; + if (particle.moveX >= dp) { + particle.xFinished = 2; + } + } else { + particle.moveX -= dp * moveCoef * 0.05f; + if (particle.moveX <= -dp) { + particle.xFinished = 1; + } + } + } else { + if (particle.right) { + if (particle.moveX < 0) { + particle.moveX += moveCoef * 0.05f; + if (particle.moveX >= 0) { + particle.moveX = 0; + particle.xFinished = particle.finishedStart; + } + } + } else { + if (particle.moveX > 0) { + particle.moveX -= moveCoef * 0.05f; + if (particle.moveX <= 0) { + particle.moveX = 0; + particle.xFinished = particle.finishedStart; + } + } + } + } + const auto yEdge = -0.5; + const auto wasNegative = (particle.moveY < yEdge); + if (particle.moveY > yEdge) { + particle.moveY += (1. / 3.) * moveCoef * _speedCoef; + } else { + particle.moveY += (1. / 3.) * moveCoef; + } + if (wasNegative && particle.moveY > yEdge) { + ++_fallingDown; + } + if (particle.type == Particle::Type::Rectangle) { + particle.rotation += moveCoef * 10; + if (particle.rotation > 360) { + particle.rotation -= 360; + } + } + if (particle.y >= kFireworkHeight) { + particle.finished = true; + } +} + +void FireworksAnimation::startFall() { + if (_startedFall) { + return; + } + _startedFall = true; + for (auto i = 0; i != kFallCount; ++i) { + initParticle(_particles.emplace_back(), true); + } +} + +void FireworksAnimation::initParticle(Particle &particle, bool falling) { + using Type = Particle::Type; + particle.color = RandomInt(_brushes.size()); + particle.type = RandomInt(2) ? Type::Rectangle : Type::Circle; + particle.right = (RandomInt(2) == 1); + particle.finishedStart = 1 + RandomInt(2); + if (particle.type == Type::Circle) { + particle.size = style::ConvertScale(6 + RandomFloat01() * 3); + } else { + particle.size = style::ConvertScale(6 + RandomFloat01() * 6); + particle.rotation = RandomInt(360); + } + if (falling) { + particle.y = -RandomFloat01() * kFireworkHeight * 1.2f; + particle.x = 5 + RandomInt(kFireworkWidth - 10); + particle.xFinished = particle.finishedStart; + } else { + const auto xOffset = 4 + RandomInt(10); + const auto yOffset = kFireworkHeight / 4; + if (particle.right) { + particle.x = kFireworkWidth + xOffset; + } else { + particle.x = -xOffset; + } + particle.moveX = (particle.right ? -1 : 1) * (1.2 + RandomFloat01() * 4); + particle.moveY = -(4 + RandomFloat01() * 4); + particle.y = yOffset / 2 + RandomInt(yOffset * 2); + } +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/fireworks_animation.h b/Telegram/SourceFiles/ui/effects/fireworks_animation.h new file mode 100644 index 000000000..168e3adbc --- /dev/null +++ b/Telegram/SourceFiles/ui/effects/fireworks_animation.h @@ -0,0 +1,63 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/effects/animations.h" + +namespace Ui { + +class FireworksAnimation final { +public: + explicit FireworksAnimation(Fn repaint); + + bool paint(QPainter &p, const QRect &rect); + +private: + struct Particle { + enum class Type : uchar { + Circle, + Rectangle + }; + + float64 x = 0.; + float64 y = 0.; + float64 moveX = 0.; + float64 moveY = 0.; + uint16 rotation = 0; + + Type type = Type::Circle; + uchar color = 0; + bool right = false; + uchar size = 0; + uchar xFinished = 0; + uchar finishedStart = 0; + bool finished = false; + }; + + void update(crl::time now); + void startFall(); + void paintParticle( + QPainter &p, + const Particle &particle, + const QRect &rect); + void initParticle(Particle &particle, bool falling); + void updateParticle(Particle &particle, crl::time dt); + + std::vector _particles; + std::vector _brushes; + Ui::Animations::Basic _animation; + Fn _repaint; + crl::time _lastUpdate = 0; + float64 _speedCoef = 1.; + int _fallingDown = 0; + int _smallSide = 0; + bool _startedFall = false; + +}; + +} // namespace Ui diff --git a/Telegram/ThirdParty/rlottie b/Telegram/ThirdParty/rlottie index 0ee2e9c58..75b31e49b 160000 --- a/Telegram/ThirdParty/rlottie +++ b/Telegram/ThirdParty/rlottie @@ -1 +1 @@ -Subproject commit 0ee2e9c5843257ccd11672611829b9bb5d02aa98 +Subproject commit 75b31e49b3c69355c4971ee2029eff23a22fcb75 diff --git a/Telegram/build/version b/Telegram/build/version index a1a828f2f..ea84f929e 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,7 +1,7 @@ -AppVersion 1009007 +AppVersion 1009008 AppVersionStrMajor 1.9 -AppVersionStrSmall 1.9.7 -AppVersionStr 1.9.7 +AppVersionStrSmall 1.9.8 +AppVersionStr 1.9.8 BetaChannel 0 AlphaVersion 0 -AppVersionOriginal 1.9.7 +AppVersionOriginal 1.9.8 diff --git a/changelog.txt b/changelog.txt index 7fa3cad89..0f1df5d4f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,7 @@ +1.9.8 (24.01.20) + +- Bug fixes and other minor improvements. + 1.9.7 (23.01.20) - Create three new kinds of polls. diff --git a/cmake b/cmake index 94bdb64c3..b944efa1f 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 94bdb64c38fc3951fd8b2ae29ba8850484df044c +Subproject commit b944efa1f33770fe88c1fb54295a84f3db4b4d26