Merge remote-tracking branch 'tdesktop/dev' into dev

This commit is contained in:
Eric Kotato 2020-01-24 21:10:15 +03:00
commit 07509deee9
28 changed files with 582 additions and 93 deletions

View file

@ -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

View file

@ -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";

View file

@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.9.7.0" />
Version="1.9.8.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram FZ-LLC</PublisherDisplayName>

View file

@ -48,8 +48,9 @@ public:
not_null<Main::Session*> session,
bool chooseCorrectEnabled);
[[nodiscard]] bool hasOptions() const;
[[nodiscard]] bool isValid() const;
[[nodiscard]] rpl::producer<bool> isValidChanged() const;
[[nodiscard]] bool hasCorrect() const;
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
void focusFirst();
@ -139,8 +140,10 @@ private:
int _position = 0;
std::vector<std::unique_ptr<Option>> _list;
std::vector<std::unique_ptr<Option>> _destroyed;
rpl::variable<bool> _valid = false;
rpl::variable<int> _usedCount = 0;
bool _hasOptions = false;
bool _isValid = false;
bool _hasCorrect = false;
rpl::event_stream<not_null<QWidget*>> _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<bool> Options::isValidChanged() const {
return _valid.changes();
bool Options::isValid() const {
return _isValid;
}
bool Options::hasCorrect() const {
return _hasCorrect;
}
rpl::producer<int> 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*> 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<Ui::RpWidget> CreatePollBox::setupContent() {
using namespace Settings;
const auto id = rand_value<uint64>();
const auto valid = lifetime().make_state<rpl::event_stream<bool>>();
const auto error = lifetime().make_state<Errors>(Error::Question);
auto result = object_ptr<Ui::VerticalLayout>(this);
const auto container = result.data();
@ -910,8 +918,41 @@ object_ptr<Ui::RpWidget> 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<Ui::RpWidget> 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<QWidget*> widget) {
@ -967,6 +978,22 @@ object_ptr<Ui::RpWidget> 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;
}

View file

@ -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<Error>;
object_ptr<Ui::RpWidget> setupContent();
not_null<Ui::InputField*> setupQuestion(
not_null<Ui::VerticalLayout*> container);

View file

@ -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;

View file

@ -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;
});

View file

@ -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<Element*> view) {

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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.

View file

@ -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) {

View file

@ -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<HistoryMessageReply>();
if (reply && reply->isNameUpdated()) {
const_cast<Message*>(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();

View file

@ -87,6 +87,8 @@ public:
int infoWidth() const override;
int plainMaxWidth() const override;
VerticalRepaintRange verticalRepaintRange() const override;
protected:
void refreshDataIdHook() override;

View file

@ -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() {
}

View file

@ -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<Ui::FireworksAnimation>(
[=] { 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<LambdaClickHandler>(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) {

View file

@ -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<Ui::RippleAnimation> _linkRipple;
bool _hasSelected = false;
mutable std::unique_ptr<AnswersAnimation> _answersAnimation;
mutable std::unique_ptr<SendingAnimation> _sendingAnimation;
mutable std::unique_ptr<Ui::FireworksAnimation> _fireworksAnimation;
Ui::Animations::Simple _wrongAnswerAnimation;
mutable QPoint _lastLinkPoint;
bool _hasSelected = false;
bool _votedFromHere = false;
mutable bool _wrongAnswerAnimated = false;
};
} // namespace HistoryView

View file

@ -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 <QDBusInterface>
#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<int>(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
}

View file

@ -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();

View file

@ -11,9 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "facades.h"
#include <QtCore/QBuffer>
#ifndef TDESKTOP_DISABLE_DBUS_INTEGRATION
#include <QtCore/QVersionNumber>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusReply>
#include <QtDBus/QDBusMetaType>
@ -101,7 +100,8 @@ NotificationData::NotificationData(
if (capabilities.contains(qsl("body-markup"))) {
_body = subtitle.isEmpty()
? msg.toHtmlEscaped()
: qsl("<b>%1</b>\n%2").arg(subtitle.toHtmlEscaped())
: qsl("<b>%1</b>\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);

View file

@ -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) {

View file

@ -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<QBrush> PrepareBrushes() {
return {
Brush(0xff2CBCE8),
Brush(0xff9E04D0),
Brush(0xffFECB02),
Brush(0xffFD2357),
Brush(0xff278CFE),
Brush(0xff59B86C),
};
}
int RandomInt(uint32 till) {
return int(openssl::RandomValue<uint32>() % till);
}
[[nodiscard]] float64 RandomFloat01() {
return openssl::RandomValue<uint32>()
/ float64(std::numeric_limits<uint32>::max());
}
} // namespace
FireworksAnimation::FireworksAnimation(Fn<void()> 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

View file

@ -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<void()> 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<Particle> _particles;
std::vector<QBrush> _brushes;
Ui::Animations::Basic _animation;
Fn<void()> _repaint;
crl::time _lastUpdate = 0;
float64 _speedCoef = 1.;
int _fallingDown = 0;
int _smallSide = 0;
bool _startedFall = false;
};
} // namespace Ui

@ -1 +1 @@
Subproject commit 0ee2e9c5843257ccd11672611829b9bb5d02aa98
Subproject commit 75b31e49b3c69355c4971ee2029eff23a22fcb75

View file

@ -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

View file

@ -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.

2
cmake

@ -1 +1 @@
Subproject commit 94bdb64c38fc3951fd8b2ae29ba8850484df044c
Subproject commit b944efa1f33770fe88c1fb54295a84f3db4b4d26