Improve notification preview settings design.

This commit is contained in:
John Preston 2022-03-08 15:23:21 +04:00
parent 4054bae9be
commit 1aa8029a8a
11 changed files with 582 additions and 288 deletions

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="126px" height="126px" viewBox="0 0 126 126" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Ava 162 Rexy</title>
<defs>
<circle id="path-1" cx="63" cy="63" r="63"></circle>
</defs>
<g id="Ava-162-Rexy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="Oval" fill="#F4D454" xlink:href="#path-1"></use>
<path d="M20.9365158,39.9285889 C24.7183624,31.4071028 27.39054,26.7087411 28.9530487,25.8335037 C30.5155574,24.9582663 32.8521129,26.5702048 35.9627151,30.6693192 C38.1674265,23.2693946 40.454073,19.3094286 42.8226547,18.7894211 C45.1912364,18.2694136 48.2904872,21.0940965 52.1204071,27.2634697 C53.2907797,18.7657681 55.6841055,14.3446116 59.3003845,14 C62.9166636,13.6553884 67.909128,17.5998897 74.2777778,25.8335037 L20.9365158,39.9285889 Z" id="Path-92" fill="#00622A" mask="url(#mask-2)"></path>
<path d="M113.382046,59.9689401 C111.646616,79.7709027 103.651333,99.2022228 68.3362932,97.2456493 L68.3340728,126.444671 C50.5706421,126.365992 36.5311584,122.520289 26.2156219,114.907562 C5.36217985,99.5180032 4.80651882,90.9000882 6.018463,89.7481658 C10.3595505,85.6220715 15.243288,82.7458807 19.9963714,81.3142806 C14.5563011,75.4154211 12.9013848,68.2050059 12.9013848,59.968468 C12.9013848,41.6878698 21.053446,28.4625028 60.6434922,23.4074139 C102.520435,18.0603208 115.237005,38.8030915 113.382046,59.9689401 Z" id="Path" fill="#00BD51" fill-rule="nonzero" mask="url(#mask-2)"></path>
<path d="M82.1644268,46.2841197 C84.4629926,46.941711 88.2595133,46.7703045 88.634112,45.6237328 C89.0087107,44.4771609 86.0738383,42.0518988 83.7752725,41.3943072 C81.4767067,40.7367156 80.6848676,42.0958112 80.3102692,43.2423831 C79.9356705,44.3889548 79.8658612,45.6265281 82.1644268,46.2841197 Z" id="Oval" fill="#00622A" fill-rule="nonzero" mask="url(#mask-2)"></path>
<path d="M96.1644268,46.2841197 C98.4629926,46.941711 102.259513,46.7703045 102.634112,45.6237328 C103.008711,44.4771609 100.073838,42.0518988 97.7752725,41.3943072 C95.4767067,40.7367156 94.6848676,42.0958112 94.3102692,43.2423831 C93.9356705,44.3889548 93.8658612,45.6265281 96.1644268,46.2841197 Z" id="Oval-Copy-2" fill="#00622A" fill-rule="nonzero" mask="url(#mask-2)" transform="translate(98.388889, 43.944444) scale(-1, 1) translate(-98.388889, -43.944444) "></path>
<path d="M44.7245297,63.7777778 C50.0939491,63.779095 54.4456778,59.7381382 54.4444444,54.7519781 C54.4430977,49.765818 52.6382331,42.5145062 47.6891674,42.0211945 C42.7401014,41.527883 34.9987102,49.7607933 35,54.7469536 C35.0012903,59.7331137 39.3551104,63.7763201 44.7245297,63.7777778 Z" id="Oval" fill="#000000" fill-rule="nonzero" mask="url(#mask-2)" transform="translate(44.722222, 52.888889) rotate(90.000000) translate(-44.722222, -52.888889) "></path>
<path d="M40.600111,57.5555556 C41.9747478,57.5555556 44.3333333,55.7107934 44.3333333,52.9186871 C44.3333333,50.1265809 41.9747478,47.4444444 40.600111,47.4444444 C39.2254743,47.4444444 38.1111111,49.7078939 38.1111111,52.5 C38.1111111,55.2921064 39.2254743,57.5555556 40.600111,57.5555556 Z" id="Oval-Copy-3" fill="#FFFFFF" fill-rule="nonzero" mask="url(#mask-2)" transform="translate(41.222222, 52.500000) rotate(14.000000) translate(-41.222222, -52.500000) "></path>
<path d="M108.111111,78.3190968 C106.65873,74.7975624 104.722222,67.2849559 100.607143,67.2849559 C96.5912923,67.2849559 95.6998306,78.3931281 90.6825397,78.5538657 C85.6652488,78.7146033 85.5989207,67.3547766 80.429916,67.3184109 C75.6746032,67.2849559 74.2222222,78.7886347 69.6230159,78.3190968 C64.8128967,77.8280266 63.0873016,73.6237177 60.6666667,66.1111111" id="Path-14" stroke="#00622A" stroke-width="4.33880817" stroke-linecap="round" mask="url(#mask-2)"></path>
<ellipse id="Oval" fill="#008539" fill-rule="nonzero" opacity="0.396437872" mask="url(#mask-2)" cx="45.5" cy="70.7777778" rx="11.2777778" ry="6.22222222"></ellipse>
<path d="M35.7777778,95.6666667 C45.2804615,95.6666667 54.2610145,93.7065293 62.2222222,90.2222222" id="Path-Copy-3" stroke="#00A747" stroke-width="4.33880817" stroke-linecap="round" mask="url(#mask-2)" transform="translate(49.000000, 92.944444) rotate(27.000000) translate(-49.000000, -92.944444) "></path>
<path d="M78.1618679,117.358247 C79.3525078,116.20628 79.3525078,114.338572 78.1618679,113.186605 L77.3961352,112.446155 L78.6178432,112.446078 C80.2765307,112.446078 81.62585,111.164538 81.6657586,109.569018 L81.6666667,109.496281 C81.6666667,107.891469 80.3421047,106.585976 78.6930217,106.547364 L78.6178432,106.546485 L76.9444278,106.54665 L78.1618679,105.368951 C79.3329891,104.235868 79.3521879,102.41032 78.2194641,101.254534 L78.1618679,101.197309 C76.9907468,100.064226 75.1039127,100.045651 73.9093257,101.141583 L73.8501796,101.197309 L67.7818776,107.068509 C67.1541415,107.675855 66.8573641,108.482154 66.8915453,109.27762 C66.8573047,110.07324 67.1540787,110.879639 67.7818776,111.487047 L73.8501796,117.358247 C75.0408195,118.510214 76.9712281,118.510214 78.1618679,117.358247 Z" id="Path" fill="#00BD51" fill-rule="nonzero" mask="url(#mask-2)"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -324,8 +324,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using.";
"lng_settings_notify_title" = "Notifications for chats";
"lng_settings_desktop_notify" = "Desktop notifications";
"lng_settings_show_name" = "Show sender's name";
"lng_settings_show_preview" = "Show message preview";
"lng_settings_native_title" = "Native notifications";
"lng_settings_use_windows" = "Use Windows notifications";
"lng_settings_use_native_notifications" = "Use native notifications";
@ -343,6 +341,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_events_pinned" = "Pinned messages";
"lng_settings_notifications_calls_title" = "Calls";
"lng_notification_preview_title" = "Dino Rex";
"lng_notification_preview_text" = "It's morning in Tokyo 😎";
"lng_notification_show_name" = "Name";
"lng_notification_show_text" = "Text";
"lng_notification_preview" = "You have a new message";
"lng_notification_reply" = "Reply";
"lng_notification_hide_all" = "Hide all";

View file

@ -67,6 +67,7 @@
<file alias="recording/info_audio.svg">../../art/recording/recording_info_audio.svg</file>
<file alias="recording/info_video_landscape.svg">../../art/recording/recording_info_video_landscape.svg</file>
<file alias="recording/info_video_portrait.svg">../../art/recording/recording_info_video_portrait.svg</file>
<file alias="icons/settings/dino.svg">../../icons/settings/dino.svg</file>
</qresource>
<qresource prefix="/icons">
<file alias="calls/hands.lottie">../../icons/calls/hands.lottie</file>

View file

@ -10,6 +10,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "window/themes/window_theme.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/chat_service_checkbox.h"
#include "ui/chat/chat_theme.h"
#include "ui/chat/chat_style.h"
#include "ui/toast/toast.h"
@ -28,9 +30,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document_resolver.h"
#include "data/data_file_origin.h"
#include "base/unixtime.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/background_preview_box.h"
#include "window/window_session_controller.h"
#include "settings/settings_common.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
@ -42,231 +44,6 @@ namespace {
constexpr auto kMaxWallPaperSlugLength = 255;
class ServiceCheck : public Ui::AbstractCheckView {
public:
ServiceCheck(const style::ServiceCheck &st, bool checked);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
private:
class Generator {
public:
Generator();
void paintFrame(
Painter &p,
int left,
int top,
not_null<const style::ServiceCheck*> st,
float64 toggled);
void invalidate();
private:
struct Frames {
QImage image;
std::vector<bool> ready;
};
not_null<Frames*> framesForStyle(
not_null<const style::ServiceCheck*> st);
static void FillFrame(
QImage &image,
not_null<const style::ServiceCheck*> st,
int index,
int count);
static void PaintFillingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress);
static void PaintCheckingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress);
base::flat_map<not_null<const style::ServiceCheck*>, Frames> _data;
rpl::lifetime _lifetime;
};
static Generator &Frames();
const style::ServiceCheck &_st;
};
ServiceCheck::Generator::Generator() {
style::PaletteChanged(
) | rpl::start_with_next([=] {
invalidate();
}, _lifetime);
}
auto ServiceCheck::Generator::framesForStyle(
not_null<const style::ServiceCheck*> st) -> not_null<Frames*> {
if (const auto i = _data.find(st); i != _data.end()) {
return &i->second;
}
const auto result = &_data.emplace(st, Frames()).first->second;
const auto size = st->diameter;
const auto count = (st->duration / AnimationTimerDelta) + 2;
result->image = QImage(
QSize(count * size, size) * cIntRetinaFactor(),
QImage::Format_ARGB32_Premultiplied);
result->image.fill(Qt::transparent);
result->image.setDevicePixelRatio(cRetinaFactor());
result->ready.resize(count);
return result;
}
void ServiceCheck::Generator::FillFrame(
QImage &image,
not_null<const style::ServiceCheck*> st,
int index,
int count) {
Expects(count > 1);
Expects(index >= 0 && index < count);
Painter p(&image);
PainterHighQualityEnabler hq(p);
p.translate(index * st->diameter, 0);
const auto progress = index / float64(count - 1);
if (progress > 0.5) {
PaintCheckingFrame(p, st, (progress - 0.5) * 2);
} else {
PaintFillingFrame(p, st, progress * 2);
}
}
void ServiceCheck::Generator::PaintFillingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress) {
const auto shift = progress * st->shift;
p.setBrush(st->color);
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF(
shift,
shift,
st->diameter - 2 * shift,
st->diameter - 2 * shift));
if (progress < 1.) {
const auto remove = progress * (st->diameter / 2. - st->thickness);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setPen(Qt::NoPen);
p.setBrush(Qt::transparent);
p.drawEllipse(QRectF(
st->thickness + remove,
st->thickness + remove,
st->diameter - 2 * (st->thickness + remove),
st->diameter - 2 * (st->thickness + remove)));
}
}
void ServiceCheck::Generator::PaintCheckingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress) {
const auto shift = (1. - progress) * st->shift;
p.setBrush(st->color);
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF(
shift,
shift,
st->diameter - 2 * shift,
st->diameter - 2 * shift));
if (progress > 0.) {
const auto tip = QPointF(st->tip.x(), st->tip.y());
const auto left = tip - QPointF(st->small, st->small) * progress;
const auto right = tip - QPointF(-st->large, st->large) * progress;
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::NoBrush);
auto pen = QPen(Qt::transparent);
pen.setWidth(st->stroke);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
p.setPen(pen);
auto path = QPainterPath();
path.moveTo(left);
path.lineTo(tip);
path.lineTo(right);
p.drawPath(path);
}
}
void ServiceCheck::Generator::paintFrame(
Painter &p,
int left,
int top,
not_null<const style::ServiceCheck*> st,
float64 toggled) {
const auto frames = framesForStyle(st);
auto &image = frames->image;
const auto count = int(frames->ready.size());
const auto index = int(base::SafeRound(toggled * (count - 1)));
Assert(index >= 0 && index < count);
if (!frames->ready[index]) {
frames->ready[index] = true;
FillFrame(image, st, index, count);
}
const auto size = st->diameter;
const auto part = size * cIntRetinaFactor();
p.drawImage(
QPoint(left, top),
image,
QRect(index * part, 0, part, part));
}
void ServiceCheck::Generator::invalidate() {
_data.clear();
}
ServiceCheck::Generator &ServiceCheck::Frames() {
static const auto Instance = Ui::CreateChild<Generator>(
QCoreApplication::instance());
return *Instance;
}
ServiceCheck::ServiceCheck(
const style::ServiceCheck &st,
bool checked)
: AbstractCheckView(st.duration, checked, nullptr)
, _st(st) {
}
QSize ServiceCheck::getSize() const {
const auto inner = QRect(0, 0, _st.diameter, _st.diameter);
return inner.marginsAdded(_st.margin).size();
}
void ServiceCheck::paint(
Painter &p,
int left,
int top,
int outerWidth) {
Frames().paintFrame(
p,
left + _st.margin.left(),
top + _st.margin.top(),
&_st,
currentAnimationValue());
}
QImage ServiceCheck::prepareRippleMask() const {
return QImage();
}
bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
return false;
}
[[nodiscard]] bool IsValidWallPaperSlug(const QString &slug) {
if (slug.isEmpty() || slug.size() > kMaxWallPaperSlugLength) {
return false;
@ -439,13 +216,13 @@ void BackgroundPreviewBox::prepare() {
}
void BackgroundPreviewBox::createBlurCheckbox() {
_blur.create(
_blur = Ui::MakeChatServiceCheckbox(
this,
tr::lng_background_blur(tr::now),
st::backgroundCheckbox,
std::make_unique<ServiceCheck>(
st::backgroundCheck,
_paper.isBlurred()));
st::backgroundCheck,
_paper.isBlurred(),
[=] { return _serviceBg.value_or(QColor(255, 255, 255, 0)); });
rpl::combine(
sizeValue(),
@ -456,20 +233,6 @@ void BackgroundPreviewBox::createBlurCheckbox() {
outer.height() - st::historyPaddingBottom - inner.height());
}, _blur->lifetime());
_blur->paintRequest(
) | rpl::filter([=] {
return _serviceBg.has_value();
}) | rpl::start_with_next([=] {
Painter p(_blur.data());
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(*_serviceBg);
p.drawRoundedRect(
_blur->rect(),
st::historyMessageRadius,
st::historyMessageRadius);
}, _blur->lifetime());
_blur->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
checkBlurAnimationStart();

View file

@ -855,14 +855,14 @@ backgroundCheckbox: Checkbox(defaultCheckbox) {
width: -50px;
margin: margins(0px, 0px, 0px, 0px);
textPosition: point(0px, 8px);
textPosition: point(0px, 6px);
checkPosition: point(0px, 0px);
style: semiboldTextStyle;
}
backgroundCheck: ServiceCheck {
margin: margins(12px, 8px, 8px, 8px);
margin: margins(10px, 6px, 8px, 6px);
diameter: 18px;
shift: 2px;
thickness: 2px;

View file

@ -369,3 +369,11 @@ settingsUsernameTop: 58px;
settingsPeerToPeerSkip: 9px;
settingsIconRadius: 6px;
notifyPreviewMargins: margins(40px, 20px, 40px, 58px);
notifyPreviewUserpicSize: 36px;
notifyPreviewUserpicPosition: point(14px, 11px);
notifyPreviewTitlePosition: point(64px, 9px);
notifyPreviewTextPosition: point(64px, 30px);
notifyPreviewChecksSkip: 12px;
notifyPreviewBottomSkip: 9px;

View file

@ -8,9 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_notifications.h"
#include "settings/settings_common.h"
#include "ui/controls/chat_service_checkbox.h"
#include "ui/effects/animations.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/box_content_divider.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/discrete_sliders.h"
@ -18,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h"
#include "window/notifications_manager.h"
#include "window/window_session_controller.h"
#include "window/section_widget.h"
#include "platform/platform_specific.h"
#include "platform/platform_notifications_manager.h"
#include "base/platform/base_platform_info.h"
@ -31,9 +34,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "facades.h"
#include "styles/style_settings.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_chat.h"
#include "styles/style_window.h"
#include "styles/style_dialogs.h"
#include <QSvgRenderer>
#include <QTimer>
namespace Settings {
@ -513,6 +519,199 @@ void NotificationsCount::SampleWidget::destroyDelayed() {
#endif // Q_OS_UNIX && !Q_OS_MAC
}
class NotifyPreview final {
public:
NotifyPreview(bool nameShown, bool previewShown);
void setNameShown(bool shown);
void setPreviewShown(bool shown);
int resizeGetHeight(int newWidth);
void paint(Painter &p, int x, int y);
private:
int _width = 0;
int _height = 0;
bool _nameShown = false;
bool _previewShown = false;
Ui::RoundRect _roundRect;
Ui::Text::String _name, _title;
Ui::Text::String _text, _preview;
QSvgRenderer _userpic;
QImage _logo;
};
NotifyPreview::NotifyPreview(bool nameShown, bool previewShown)
: _nameShown(nameShown)
, _previewShown(previewShown)
, _roundRect(st::boxRadius, st::msgInBg)
, _userpic(u":/gui/icons/settings/dino.svg"_q)
, _logo(Window::LogoNoMargin()) {
const auto ratio = style::DevicePixelRatio();
_logo = _logo.scaledToWidth(
st::notifyPreviewUserpicSize * ratio,
Qt::SmoothTransformation);
_logo.setDevicePixelRatio(ratio);
_name.setText(
st::settingsSubsectionTitle.style,
tr::lng_notification_preview_title(tr::now));
_title.setText(st::settingsSubsectionTitle.style, AppName.utf16());
_text.setText(
st::boxTextStyle,
tr::lng_notification_preview_text(tr::now));
_preview.setText(
st::boxTextStyle,
tr::lng_notification_preview(tr::now));
}
void NotifyPreview::setNameShown(bool shown) {
_nameShown = shown;
}
void NotifyPreview::setPreviewShown(bool shown) {
_previewShown = shown;
}
int NotifyPreview::resizeGetHeight(int newWidth) {
_width = newWidth;
_height = st::notifyPreviewUserpicPosition.y()
+ st::notifyPreviewUserpicSize
+ st::notifyPreviewUserpicPosition.y();
const auto available = _width
- st::notifyPreviewTextPosition.x()
- st::notifyPreviewUserpicPosition.x();
if (std::max(_text.maxWidth(), _preview.maxWidth()) >= available) {
_height += st::defaultTextStyle.font->height;
}
return _height;
}
void NotifyPreview::paint(Painter &p, int x, int y) {
if (!_width || !_height) {
return;
}
p.translate(x, y);
const auto guard = gsl::finally([&] { p.translate(-x, -y); });
_roundRect.paint(p, { 0, 0, _width, _height });
const auto userpic = QRect(
st::notifyPreviewUserpicPosition,
QSize{ st::notifyPreviewUserpicSize, st::notifyPreviewUserpicSize });
if (_nameShown) {
_userpic.render(&p, QRectF(userpic));
} else {
p.drawImage(userpic.topLeft(), _logo);
}
const auto &title = _nameShown ? _name : _title;
title.drawElided(
p,
st::notifyPreviewTitlePosition.x(),
st::notifyPreviewTitlePosition.y(),
_width - st::notifyPreviewTitlePosition.x() - userpic.x());
const auto &text = _previewShown ? _text : _preview;
text.drawElided(
p,
st::notifyPreviewTextPosition.x(),
st::notifyPreviewTextPosition.y(),
_width - st::notifyPreviewTextPosition.x() - userpic.x(),
2);
}
struct NotifyViewCheckboxes {
not_null<Ui::SlideWrap<>*> wrap;
not_null<Ui::Checkbox*> name;
not_null<Ui::Checkbox*> preview;
};
NotifyViewCheckboxes SetupNotifyViewOptions(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
bool nameShown,
bool previewShown) {
using namespace rpl::mappers;
auto wrap = container->add(object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::RpWidget>(container)));
const auto widget = wrap->entity();
const auto makeCheckbox = [&](const QString &text, bool checked) {
return Ui::MakeChatServiceCheckbox(
widget,
text,
st::backgroundCheckbox,
st::backgroundCheck,
checked).release();
};
const auto name = makeCheckbox(
tr::lng_notification_show_name(tr::now),
nameShown);
const auto preview = makeCheckbox(
tr::lng_notification_show_text(tr::now),
previewShown);
const auto view = widget->lifetime().make_state<NotifyPreview>(
nameShown,
previewShown);
widget->widthValue(
) | rpl::filter(
_1 >= (st::historyMinimalWidth / 2)
) | rpl::start_with_next([=](int width) {
const auto margins = st::notifyPreviewMargins;
const auto bubblew = width - margins.left() - margins.right();
const auto bubbleh = view->resizeGetHeight(bubblew);
const auto height = bubbleh + margins.top() + margins.bottom();
widget->resize(width, height);
const auto skip = st::notifyPreviewChecksSkip;
const auto checksWidth = name->width() + skip + preview->width();
const auto checksLeft = (width - checksWidth) / 2;
const auto checksTop = height
- (margins.bottom() + name->height()) / 2;
name->move(checksLeft, checksTop);
preview->move(checksLeft + name->width() + skip, checksTop);
}, widget->lifetime());
widget->paintRequest(
) | rpl::start_with_next([=](QRect rect) {
Window::SectionWidget::PaintBackground(
controller,
controller->defaultChatTheme().get(), // #TODO themes
widget,
rect);
Painter p(widget);
view->paint(
p,
st::notifyPreviewMargins.left(),
st::notifyPreviewMargins.top());
}, widget->lifetime());
name->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
view->setNameShown(checked);
widget->update();
}, name->lifetime());
preview->checkedChanges(
) | rpl::start_with_next([=](bool checked) {
view->setPreviewShown(checked);
widget->update();
}, preview->lifetime());
return {
.wrap = wrap,
.name = name,
.preview = preview,
};
}
void SetupAdvancedNotifications(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
@ -615,18 +814,6 @@ void SetupNotificationsContent(
std::move(descriptor),
std::move(checked)));
};
const auto addSlidingCheckbox = [&](
rpl::producer<QString> label,
IconDescriptor &&descriptor,
rpl::producer<bool> checked) {
return container->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
container,
checkbox(
std::move(label),
std::move(descriptor),
std::move(checked))));
};
const auto &settings = Core::App().settings();
const auto desktopToggles = container->lifetime(
).make_state<rpl::event_stream<bool>>();
@ -635,15 +822,6 @@ void SetupNotificationsContent(
{ &st::settingsIconNotifications, kIconRed },
desktopToggles->events_starting_with(settings.desktopNotify()));
const auto name = addSlidingCheckbox(
tr::lng_settings_show_name(),
{ &st::settingsIconUser, kIconLightOrange },
rpl::single(settings.notifyView() <= NotifyView::ShowName));
const auto preview = addSlidingCheckbox(
tr::lng_settings_show_preview(),
{ &st::settingsIconAskQuestion, kIconGreen },
rpl::single(settings.notifyView() <= NotifyView::ShowPreview));
const auto soundToggles = container->lifetime(
).make_state<rpl::event_stream<bool>>();
const auto sound = addCheckbox(
@ -663,8 +841,23 @@ void SetupNotificationsContent(
settings.flashBounceNotify()));
AddSkip(container);
AddDivider(container);
AddSkip(container);
const auto checkboxes = SetupNotifyViewOptions(
controller,
container,
(settings.notifyView() <= NotifyView::ShowName),
(settings.notifyView() <= NotifyView::ShowPreview));
const auto name = checkboxes.name;
const auto preview = checkboxes.preview;
const auto previewWrap = checkboxes.wrap;
const auto previewDivider = container->add(
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
container,
object_ptr<Ui::BoxContentDivider>(container)));
previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
AddSkip(container, st::notifyPreviewBottomSkip);
AddSubsectionTitle(container, tr::lng_settings_events_title());
auto joinSilent = rpl::single(
@ -768,13 +961,6 @@ void SetupNotificationsContent(
SetupAdvancedNotifications(controller, advancedWrap);
}
if (!name->entity()->toggled()) {
preview->hide(anim::type::instant);
}
if (!desktop->toggled()) {
name->hide(anim::type::instant);
preview->hide(anim::type::instant);
}
if (native && advancedSlide && settings.nativeNotifications()) {
advancedSlide->hide(anim::type::instant);
}
@ -792,11 +978,12 @@ void SetupNotificationsContent(
changed(Change::DesktopEnabled);
}, desktop->lifetime());
name->entity()->toggledChanges(
name->checkedChanges(
) | rpl::map([=](bool checked) {
if (!checked) {
preview->setChecked(false);
return NotifyView::ShowNothing;
} else if (!preview->entity()->toggled()) {
} else if (!preview->checked()) {
return NotifyView::ShowName;
}
return NotifyView::ShowPreview;
@ -807,11 +994,12 @@ void SetupNotificationsContent(
changed(Change::ViewParams);
}, name->lifetime());
preview->entity()->toggledChanges(
preview->checkedChanges(
) | rpl::map([=](bool checked) {
if (checked) {
name->setChecked(true);
return NotifyView::ShowPreview;
} else if (name->entity()->toggled()) {
} else if (name->checked()) {
return NotifyView::ShowName;
}
return NotifyView::ShowNothing;
@ -858,15 +1046,14 @@ void SetupNotificationsContent(
) | rpl::start_with_next([=](Change change) {
if (change == Change::DesktopEnabled) {
desktopToggles->fire(Core::App().settings().desktopNotify());
name->toggle(
previewWrap->toggle(
Core::App().settings().desktopNotify(),
anim::type::normal);
preview->toggle(
(Core::App().settings().desktopNotify()
&& name->entity()->toggled()),
previewDivider->toggle(
!Core::App().settings().desktopNotify(),
anim::type::normal);
} else if (change == Change::ViewParams) {
preview->toggle(name->entity()->toggled(), anim::type::normal);
//
} else if (change == Change::SoundEnabled) {
soundToggles->fire(Core::App().settings().soundNotify());
} else if (change == Change::FlashBounceEnabled) {

View file

@ -0,0 +1,279 @@
/*
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/controls/chat_service_checkbox.h"
#include "ui/widgets/checkbox.h"
#include "styles/style_layers.h"
#include <QCoreApplication>
namespace Ui {
namespace {
constexpr auto kAnimationTimerDelta = crl::time(7);
class ServiceCheck final : public AbstractCheckView {
public:
ServiceCheck(const style::ServiceCheck &st, bool checked);
QSize getSize() const override;
void paint(
Painter &p,
int left,
int top,
int outerWidth) override;
QImage prepareRippleMask() const override;
bool checkRippleStartPosition(QPoint position) const override;
private:
class Generator {
public:
Generator();
void paintFrame(
Painter &p,
int left,
int top,
not_null<const style::ServiceCheck*> st,
float64 toggled);
void invalidate();
private:
struct Frames {
QImage image;
std::vector<bool> ready;
};
not_null<Frames*> framesForStyle(
not_null<const style::ServiceCheck*> st);
static void FillFrame(
QImage &image,
not_null<const style::ServiceCheck*> st,
int index,
int count);
static void PaintFillingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress);
static void PaintCheckingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress);
base::flat_map<not_null<const style::ServiceCheck*>, Frames> _data;
rpl::lifetime _lifetime;
};
static Generator &Frames();
const style::ServiceCheck &_st;
};
ServiceCheck::Generator::Generator() {
style::PaletteChanged(
) | rpl::start_with_next([=] {
invalidate();
}, _lifetime);
}
auto ServiceCheck::Generator::framesForStyle(
not_null<const style::ServiceCheck*> st) -> not_null<Frames*> {
if (const auto i = _data.find(st); i != _data.end()) {
return &i->second;
}
const auto result = &_data.emplace(st, Frames()).first->second;
const auto size = st->diameter;
const auto count = (st->duration / kAnimationTimerDelta) + 2;
result->image = QImage(
QSize(count * size, size) * style::DevicePixelRatio(),
QImage::Format_ARGB32_Premultiplied);
result->image.fill(Qt::transparent);
result->image.setDevicePixelRatio(style::DevicePixelRatio());
result->ready.resize(count);
return result;
}
void ServiceCheck::Generator::FillFrame(
QImage &image,
not_null<const style::ServiceCheck*> st,
int index,
int count) {
Expects(count > 1);
Expects(index >= 0 && index < count);
Painter p(&image);
PainterHighQualityEnabler hq(p);
p.translate(index * st->diameter, 0);
const auto progress = index / float64(count - 1);
if (progress > 0.5) {
PaintCheckingFrame(p, st, (progress - 0.5) * 2);
} else {
PaintFillingFrame(p, st, progress * 2);
}
}
void ServiceCheck::Generator::PaintFillingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress) {
const auto shift = progress * st->shift;
p.setBrush(st->color);
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF(
shift,
shift,
st->diameter - 2 * shift,
st->diameter - 2 * shift));
if (progress < 1.) {
const auto remove = progress * (st->diameter / 2. - st->thickness);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setPen(Qt::NoPen);
p.setBrush(Qt::transparent);
p.drawEllipse(QRectF(
st->thickness + remove,
st->thickness + remove,
st->diameter - 2 * (st->thickness + remove),
st->diameter - 2 * (st->thickness + remove)));
}
}
void ServiceCheck::Generator::PaintCheckingFrame(
Painter &p,
not_null<const style::ServiceCheck*> st,
float64 progress) {
const auto shift = (1. - progress) * st->shift;
p.setBrush(st->color);
p.setPen(Qt::NoPen);
p.drawEllipse(QRectF(
shift,
shift,
st->diameter - 2 * shift,
st->diameter - 2 * shift));
if (progress > 0.) {
const auto tip = QPointF(st->tip.x(), st->tip.y());
const auto left = tip - QPointF(st->small, st->small) * progress;
const auto right = tip - QPointF(-st->large, st->large) * progress;
p.setCompositionMode(QPainter::CompositionMode_Source);
p.setBrush(Qt::NoBrush);
auto pen = QPen(Qt::transparent);
pen.setWidth(st->stroke);
pen.setCapStyle(Qt::RoundCap);
pen.setJoinStyle(Qt::RoundJoin);
p.setPen(pen);
auto path = QPainterPath();
path.moveTo(left);
path.lineTo(tip);
path.lineTo(right);
p.drawPath(path);
}
}
void ServiceCheck::Generator::paintFrame(
Painter &p,
int left,
int top,
not_null<const style::ServiceCheck*> st,
float64 toggled) {
const auto frames = framesForStyle(st);
auto &image = frames->image;
const auto count = int(frames->ready.size());
const auto index = int(base::SafeRound(toggled * (count - 1)));
Assert(index >= 0 && index < count);
if (!frames->ready[index]) {
frames->ready[index] = true;
FillFrame(image, st, index, count);
}
const auto size = st->diameter;
const auto part = size * style::DevicePixelRatio();
p.drawImage(
QPoint(left, top),
image,
QRect(index * part, 0, part, part));
}
void ServiceCheck::Generator::invalidate() {
_data.clear();
}
ServiceCheck::Generator &ServiceCheck::Frames() {
static const auto Instance = Ui::CreateChild<Generator>(
QCoreApplication::instance());
return *Instance;
}
ServiceCheck::ServiceCheck(
const style::ServiceCheck &st,
bool checked)
: AbstractCheckView(st.duration, checked, nullptr)
, _st(st) {
}
QSize ServiceCheck::getSize() const {
const auto inner = QRect(0, 0, _st.diameter, _st.diameter);
return inner.marginsAdded(_st.margin).size();
}
void ServiceCheck::paint(
Painter &p,
int left,
int top,
int outerWidth) {
Frames().paintFrame(
p,
left + _st.margin.left(),
top + _st.margin.top(),
&_st,
currentAnimationValue());
}
QImage ServiceCheck::prepareRippleMask() const {
return QImage();
}
bool ServiceCheck::checkRippleStartPosition(QPoint position) const {
return false;
}
void SetupBackground(not_null<Checkbox*> checkbox, Fn<QColor()> bg) {
checkbox->paintRequest(
) | rpl::map(
bg ? bg : [] { return st::msgServiceBg->c; }
) | rpl::filter([=](const QColor &color) {
return color.alpha() > 0;
}) | rpl::start_with_next([=](const QColor &color) {
Painter p(checkbox);
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(color);
const auto radius = checkbox->height() / 2.;
p.drawRoundedRect(checkbox->rect(), radius, radius);
}, checkbox->lifetime());
}
} // namespace
[[nodiscard]] object_ptr<Checkbox> MakeChatServiceCheckbox(
QWidget *parent,
const QString &text,
const style::Checkbox &st,
const style::ServiceCheck &stCheck,
bool checked,
Fn<QColor()> bg) {
auto result = object_ptr<Checkbox>(
parent,
text,
st,
std::make_unique<ServiceCheck>(stCheck, checked));
SetupBackground(result.data(), std::move(bg));
return result;
}
} // namespace Ui

View file

@ -0,0 +1,29 @@
/*
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 "base/object_ptr.h"
namespace style {
struct Checkbox;
struct ServiceCheck;
} // namespace style
namespace Ui {
class Checkbox;
[[nodiscard]] object_ptr<Checkbox> MakeChatServiceCheckbox(
QWidget *parent,
const QString &text,
const style::Checkbox &st,
const style::ServiceCheck &stCheck,
bool checked,
Fn<QColor()> bg = nullptr);
} // namespace Ui

View file

@ -954,7 +954,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
&& (item->out() || peer->isSelf())
&& item->isFromScheduled();
const auto title = options.hideNameAndPhoto
? qsl("Telegram Desktop")
? AppName.utf16()
: (scheduled && peer->isSelf())
? tr::lng_notification_reminder(tr::now)
: peer->name;

View file

@ -190,6 +190,8 @@ PRIVATE
ui/chat/select_scroll_manager.h
ui/controls/call_mute_button.cpp
ui/controls/call_mute_button.h
ui/controls/chat_service_checkbox.cpp
ui/controls/chat_service_checkbox.h
ui/controls/delete_message_context_action.cpp
ui/controls/delete_message_context_action.h
ui/controls/download_bar.cpp