Files
AyuGramDesktop/Telegram/SourceFiles/settings/sections/settings_chat.cpp
T
2026-01-28 11:55:19 +04:00

2682 lines
74 KiB
C++

/*
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 "settings/sections/settings_chat.h"
#include "base/timer_rpl.h"
#include "settings/builder/settings_builder.h"
#include "settings/sections/settings_advanced.h"
#include "settings/sections/settings_main.h"
#include "settings/settings_privacy_security.h"
#include "settings/settings_experimental.h"
#include "settings/settings_shortcuts.h"
#include "boxes/abstract_box.h"
#include "boxes/peers/edit_peer_color_box.h"
#include "boxes/connection_box.h"
#include "boxes/auto_download_box.h"
#include "boxes/reactions_settings_box.h"
#include "boxes/stickers_box.h"
#include "ui/boxes/confirm_box.h"
#include "boxes/background_box.h"
#include "boxes/background_preview_box.h"
#include "boxes/download_path_box.h"
#include "boxes/local_storage_box.h"
#include "dialogs/ui/dialogs_quick_action_context.h"
#include "dialogs/dialogs_quick_action.h"
#include "ui/boxes/choose_font_box.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/color_editor.h"
#include "ui/widgets/labels.h"
#include "ui/chat/attach/attach_extensions.h"
#include "ui/chat/chat_style.h"
#include "ui/chat/chat_theme.h"
#include "ui/layers/generic_box.h"
#include "ui/effects/radial_animation.h"
#include "ui/style/style_palette_colorizer.h"
#include "ui/toast/toast.h"
#include "ui/image/image.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h"
#include "ui/ui_utility.h"
#include "ui/widgets/menu/menu_add_action_callback.h"
#include "history/view/history_view_quick_action.h"
#include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "export/export_manager.h"
#include "window/themes/window_theme.h"
#include "window/themes/window_themes_embedded.h"
#include "window/themes/window_theme_editor_box.h"
#include "window/themes/window_themes_cloud_list.h"
#include "window/window_adaptive.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "info/downloads/info_downloads_widget.h"
#include "info/info_memento.h"
#include "storage/localstorage.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_session.h"
#include "data/data_cloud_themes.h"
#include "data/data_file_origin.h"
#include "data/data_message_reactions.h"
#include "data/data_peer_values.h"
#include "data/data_user.h"
#include "chat_helpers/emoji_sets_manager.h"
#include "base/platform/base_platform_info.h"
#include "base/call_delayed.h"
#include "support/support_common.h"
#include "support/support_templates.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "mainwidget.h"
#include "styles/style_chat_helpers.h" // stickersRemove
#include "styles/style_settings.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_window.h"
#include "styles/style_dialogs.h"
#include <QAction>
namespace Settings {
namespace {
using namespace Builder;
const auto kSchemesList = Window::Theme::EmbeddedThemes();
constexpr auto kCustomColorButtonParts = 7;
class ColorsPalette final {
public:
using Type = Window::Theme::EmbeddedType;
using Scheme = Window::Theme::EmbeddedScheme;
explicit ColorsPalette(not_null<Ui::VerticalLayout*> container);
void show(Type type);
[[nodiscard]] Ui::RpWidget *editButton() const;
rpl::producer<QColor> selected() const;
private:
class Button {
public:
Button(
not_null<QWidget*> parent,
std::vector<QColor> &&colors,
bool selected);
void moveToLeft(int x, int y);
void update(std::vector<QColor> &&colors, bool selected);
rpl::producer<> clicks() const;
bool selected() const;
QColor color() const;
Ui::RpWidget *widget() const;
private:
void paint();
Ui::AbstractButton _widget;
std::vector<QColor> _colors;
Ui::Animations::Simple _selectedAnimation;
bool _selected = false;
};
void show(
not_null<const Scheme*> scheme,
std::vector<QColor> &&colors,
int selected);
void selectCustom(not_null<const Scheme*> scheme);
void updateInnerGeometry();
not_null<Ui::SlideWrap<>*> _outer;
std::vector<std::unique_ptr<Button>> _buttons;
rpl::event_stream<QColor> _selected;
};
void PaintCustomButton(QPainter &p, const std::vector<QColor> &colors) {
Expects(colors.size() >= kCustomColorButtonParts);
p.setPen(Qt::NoPen);
const auto size = st::settingsAccentColorSize;
const auto smallSize = size / 8.;
const auto drawAround = [&](QPointF center, int index) {
const auto where = QPointF{
size * (1. + center.x()) / 2,
size * (1. + center.y()) / 2
};
p.setBrush(colors[index]);
p.drawEllipse(
where.x() - smallSize,
where.y() - smallSize,
2 * smallSize,
2 * smallSize);
};
drawAround(QPointF(), 0);
for (auto i = 0; i != 6; ++i) {
const auto angle = i * M_PI / 3.;
const auto point = QPointF{ cos(angle), sin(angle) };
const auto adjusted = point * (1. - (2 * smallSize / size));
drawAround(adjusted, i + 1);
}
}
ColorsPalette::Button::Button(
not_null<QWidget*> parent,
std::vector<QColor> &&colors,
bool selected)
: _widget(parent.get())
, _colors(std::move(colors))
, _selected(selected) {
_widget.show();
_widget.resize(st::settingsAccentColorSize, st::settingsAccentColorSize);
_widget.paintRequest(
) | rpl::on_next([=] {
paint();
}, _widget.lifetime());
}
void ColorsPalette::Button::moveToLeft(int x, int y) {
_widget.moveToLeft(x, y);
}
void ColorsPalette::Button::update(
std::vector<QColor> &&colors,
bool selected) {
if (_colors != colors) {
_colors = std::move(colors);
_widget.update();
}
if (_selected != selected) {
_selected = selected;
_selectedAnimation.start(
[=] { _widget.update(); },
_selected ? 0. : 1.,
_selected ? 1. : 0.,
st::defaultRadio.duration * 2);
}
}
rpl::producer<> ColorsPalette::Button::clicks() const {
return _widget.clicks() | rpl::to_empty;
}
bool ColorsPalette::Button::selected() const {
return _selected;
}
QColor ColorsPalette::Button::color() const {
Expects(_colors.size() == 1);
return _colors.front();
}
Ui::RpWidget *ColorsPalette::Button::widget() const {
return const_cast<Ui::AbstractButton*>(&_widget);
}
void ColorsPalette::Button::paint() {
auto p = QPainter(&_widget);
PainterHighQualityEnabler hq(p);
if (_colors.size() == 1) {
PaintRoundColorButton(
p,
st::settingsAccentColorSize,
_colors.front(),
_selectedAnimation.value(_selected ? 1. : 0.));
} else if (_colors.size() >= kCustomColorButtonParts) {
PaintCustomButton(p, _colors);
}
}
ColorsPalette::ColorsPalette(not_null<Ui::VerticalLayout*> container)
: _outer(container->add(
object_ptr<Ui::SlideWrap<>>(
container,
object_ptr<Ui::RpWidget>(container)))) {
_outer->hide(anim::type::instant);
const auto inner = _outer->entity();
inner->widthValue(
) | rpl::on_next([=] {
updateInnerGeometry();
}, inner->lifetime());
}
Ui::RpWidget *ColorsPalette::editButton() const {
return _buttons.empty() ? nullptr : _buttons.back()->widget();
}
void ColorsPalette::show(Type type) {
const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
if (scheme == end(kSchemesList)) {
_outer->hide(anim::type::instant);
return;
}
auto list = Window::Theme::DefaultAccentColors(type);
if (list.empty()) {
_outer->hide(anim::type::instant);
return;
}
list.insert(list.begin(), scheme->accentColor);
const auto color = Core::App().settings().themesAccentColors().get(type);
const auto current = color.value_or(scheme->accentColor);
const auto i = ranges::find(list, current);
if (i == end(list)) {
list.back() = current;
}
const auto selected = std::clamp(
int(i - begin(list)),
0,
int(list.size()) - 1);
_outer->show(anim::type::instant);
show(&*scheme, std::move(list), selected);
const auto inner = _outer->entity();
inner->resize(_outer->width(), inner->height());
updateInnerGeometry();
}
void ColorsPalette::show(
not_null<const Scheme*> scheme,
std::vector<QColor> &&colors,
int selected) {
Expects(selected >= 0 && selected < colors.size());
while (_buttons.size() > colors.size()) {
_buttons.pop_back();
}
auto index = 0;
const auto inner = _outer->entity();
const auto pushButton = [&](std::vector<QColor> &&colors) {
auto result = rpl::producer<>();
const auto chosen = (index == selected);
if (_buttons.size() > index) {
_buttons[index]->update(std::move(colors), chosen);
} else {
_buttons.push_back(std::make_unique<Button>(
inner,
std::move(colors),
chosen));
result = _buttons.back()->clicks();
}
++index;
return result;
};
for (const auto &color : colors) {
auto clicks = pushButton({ color });
if (clicks) {
std::move(
clicks
) | rpl::map([=] {
return _buttons[index - 1]->color();
}) | rpl::on_next([=](QColor color) {
_selected.fire_copy(color);
}, inner->lifetime());
}
}
auto clicks = pushButton(std::move(colors));
if (clicks) {
std::move(
clicks
) | rpl::on_next([=] {
selectCustom(scheme);
}, inner->lifetime());
}
}
void ColorsPalette::selectCustom(not_null<const Scheme*> scheme) {
const auto selected = ranges::find(_buttons, true, &Button::selected);
Assert(selected != end(_buttons));
const auto colorizer = Window::Theme::ColorizerFrom(
*scheme,
scheme->accentColor);
Ui::show(Box([=](not_null<Ui::GenericBox*> box) {
const auto editor = box->addRow(object_ptr<ColorEditor>(
box,
ColorEditor::Mode::HSL,
(*selected)->color()));
const auto save = crl::guard(_outer, [=] {
_selected.fire_copy(editor->color());
box->closeBox();
});
editor->submitRequests(
) | rpl::on_next(save, editor->lifetime());
editor->setLightnessLimits(
colorizer.lightnessMin,
colorizer.lightnessMax);
box->setFocusCallback([=] {
editor->setInnerFocus();
});
box->addButton(tr::lng_settings_save(), save);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
box->setTitle(tr::lng_settings_theme_accent_title());
box->setWidth(editor->width());
}));
}
rpl::producer<QColor> ColorsPalette::selected() const {
return _selected.events();
}
void ColorsPalette::updateInnerGeometry() {
const auto count = int(_buttons.size());
if (count < 2) {
return;
}
const auto inner = _outer->entity();
const auto size = st::settingsAccentColorSize;
const auto padding = st::settingsButtonNoIcon.padding;
const auto width = inner->width() - padding.left() - padding.right();
if (width < size * count) {
return;
}
const auto skip = (width - size * count) / float64(count - 1);
const auto y = st::defaultVerticalListSkip * 2;
auto x = float64(padding.left());
for (const auto &button : _buttons) {
button->moveToLeft(int(base::SafeRound(x)), y);
x += size + skip;
}
inner->resize(inner->width(), y + size + st::defaultVerticalListSkip);
}
class BackgroundRow : public Ui::RpWidget {
public:
BackgroundRow(
QWidget *parent,
not_null<Window::SessionController*> controller);
[[nodiscard]] Ui::LinkButton *chooseFromGallery() const;
[[nodiscard]] Ui::LinkButton *chooseFromFile() const;
protected:
void paintEvent(QPaintEvent *e) override;
int resizeGetHeight(int newWidth) override;
private:
void updateImage();
float64 radialProgress() const;
bool radialLoading() const;
QRect radialRect() const;
void radialStart();
crl::time radialTimeShift() const;
void radialAnimationCallback(crl::time now);
const not_null<Window::SessionController*> _controller;
QPixmap _background;
object_ptr<Ui::LinkButton> _chooseFromGallery;
object_ptr<Ui::LinkButton> _chooseFromFile;
Ui::RadialAnimation _radial;
};
void ChooseFromFile(
not_null<Window::SessionController*> controller,
not_null<QWidget*> parent);
BackgroundRow::BackgroundRow(
QWidget *parent,
not_null<Window::SessionController*> controller)
: RpWidget(parent)
, _controller(controller)
, _chooseFromGallery(
this,
tr::lng_settings_bg_from_gallery(tr::now),
st::settingsLink)
, _chooseFromFile(this, tr::lng_settings_bg_from_file(tr::now), st::settingsLink)
, _radial([=](crl::time now) { radialAnimationCallback(now); }) {
updateImage();
_chooseFromGallery->addClickHandler([=] {
controller->show(Box<BackgroundBox>(controller));
});
_chooseFromFile->addClickHandler([=] {
ChooseFromFile(controller, this);
});
using Update = const Window::Theme::BackgroundUpdate;
Window::Theme::Background()->updates(
) | rpl::filter([](const Update &update) {
return (update.type == Update::Type::New
|| update.type == Update::Type::Start
|| update.type == Update::Type::Changed);
}) | rpl::on_next([=] {
updateImage();
}, lifetime());
}
void BackgroundRow::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto radial = _radial.animating();
const auto radialOpacity = radial ? _radial.opacity() : 0.;
if (radial) {
const auto backThumb = _controller->content()->newBackgroundThumb();
if (!backThumb) {
p.drawPixmap(0, 0, _background);
} else {
const auto &pix = backThumb->pix(
st::settingsBackgroundThumb,
{ .options = Images::Option::Blur });
const auto factor = style::DevicePixelRatio();
p.drawPixmap(
0,
0,
st::settingsBackgroundThumb,
st::settingsBackgroundThumb,
pix,
0,
(pix.height() - st::settingsBackgroundThumb * factor) / 2,
st::settingsBackgroundThumb * factor,
st::settingsBackgroundThumb * factor);
}
const auto outer = radialRect();
const auto inner = QRect(
QPoint(
outer.x() + (outer.width() - st::radialSize.width()) / 2,
outer.y() + (outer.height() - st::radialSize.height()) / 2),
st::radialSize);
p.setPen(Qt::NoPen);
p.setOpacity(radialOpacity);
p.setBrush(st::radialBg);
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
p.setOpacity(1);
const auto arc = inner.marginsRemoved(QMargins(
st::radialLine,
st::radialLine,
st::radialLine,
st::radialLine));
_radial.draw(p, arc, st::radialLine, st::radialFg);
} else {
p.drawPixmap(0, 0, _background);
}
}
int BackgroundRow::resizeGetHeight(int newWidth) {
auto linkTop = st::settingsFromGalleryTop;
auto linkLeft = st::settingsBackgroundThumb + st::settingsThumbSkip;
auto linkWidth = newWidth - linkLeft;
_chooseFromGallery->resizeToWidth(
qMin(linkWidth, _chooseFromGallery->naturalWidth()));
_chooseFromFile->resizeToWidth(
qMin(linkWidth, _chooseFromFile->naturalWidth()));
_chooseFromGallery->moveToLeft(linkLeft, linkTop, newWidth);
linkTop += _chooseFromGallery->height() + st::settingsFromFileTop;
_chooseFromFile->moveToLeft(linkLeft, linkTop, newWidth);
return st::settingsBackgroundThumb;
}
Ui::LinkButton *BackgroundRow::chooseFromGallery() const {
return _chooseFromGallery.data();
}
Ui::LinkButton *BackgroundRow::chooseFromFile() const {
return _chooseFromFile.data();
}
float64 BackgroundRow::radialProgress() const {
return _controller->content()->chatBackgroundProgress();
}
bool BackgroundRow::radialLoading() const {
const auto widget = _controller->content();
if (widget->chatBackgroundLoading()) {
widget->checkChatBackground();
if (widget->chatBackgroundLoading()) {
return true;
} else {
const_cast<BackgroundRow*>(this)->updateImage();
}
}
return false;
}
QRect BackgroundRow::radialRect() const {
return QRect(
0,
0,
st::settingsBackgroundThumb,
st::settingsBackgroundThumb);
}
void BackgroundRow::radialStart() {
if (radialLoading() && !_radial.animating()) {
_radial.start(radialProgress());
if (const auto shift = radialTimeShift()) {
_radial.update(
radialProgress(),
!radialLoading(),
crl::now() + shift);
}
}
}
crl::time BackgroundRow::radialTimeShift() const {
return st::radialDuration;
}
void BackgroundRow::radialAnimationCallback(crl::time now) {
const auto updated = _radial.update(
radialProgress(),
!radialLoading(),
now + radialTimeShift());
if (!anim::Disabled() || updated) {
rtlupdate(radialRect());
}
}
void BackgroundRow::updateImage() {
const auto size = st::settingsBackgroundThumb;
const auto fullsize = size * style::DevicePixelRatio();
const auto &background = *Window::Theme::Background();
const auto &paper = background.paper();
const auto &prepared = background.prepared();
const auto preparePattern = [&] {
const auto paintPattern = [&](QPainter &p, bool inverted) {
if (prepared.isNull()) {
return;
}
const auto w = prepared.width();
const auto h = prepared.height();
const auto s = [&] {
const auto scaledw = w * st::windowMinHeight / h;
const auto result = (w * size) / scaledw;
return std::min({ result, w, h });
}();
auto small = prepared.copy((w - s) / 2, (h - s) / 2, s, s);
if (inverted) {
small = Ui::InvertPatternImage(std::move(small));
}
p.drawImage(QRect(0, 0, fullsize, fullsize), small);
};
return Ui::GenerateBackgroundImage(
{ fullsize, fullsize },
paper.backgroundColors(),
paper.gradientRotation(),
paper.patternOpacity(),
paintPattern);
};
const auto prepareNormal = [&] {
auto result = QImage(
QSize{ fullsize, fullsize },
QImage::Format_ARGB32_Premultiplied);
result.setDevicePixelRatio(style::DevicePixelRatio());
if (const auto color = background.colorForFill()) {
result.fill(*color);
return result;
} else if (prepared.isNull()) {
result.fill(Qt::transparent);
return result;
}
auto p = QPainter(&result);
PainterHighQualityEnabler hq(p);
const auto w = prepared.width();
const auto h = prepared.height();
const auto s = std::min(w, h);
p.drawImage(
QRect(0, 0, size, size),
prepared,
QRect((w - s) / 2, (h - s) / 2, s, s));
p.end();
return result;
};
auto back = (paper.isPattern() || !background.gradientForFill().isNull())
? preparePattern()
: prepareNormal();
_background = Ui::PixmapFromImage(
Images::Round(std::move(back), ImageRoundRadius::Small));
_background.setDevicePixelRatio(style::DevicePixelRatio());
rtlupdate(radialRect());
if (radialLoading()) {
radialStart();
}
}
void ChooseFromFile(
not_null<Window::SessionController*> controller,
not_null<QWidget*> parent) {
auto filters = QStringList(
u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q
+ Ui::ImageExtensions().join(u" *"_q)
+ u")"_q);
filters.push_back(FileDialog::AllFilesFilter());
const auto callback = crl::guard(controller, [=](
const FileDialog::OpenResult &result) {
if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
return;
}
if (!result.paths.isEmpty()) {
const auto filePath = result.paths.front();
const auto hasExtension = [&](QLatin1String extension) {
return filePath.endsWith(extension, Qt::CaseInsensitive);
};
if (hasExtension(qstr(".tdesktop-theme"))
|| hasExtension(qstr(".tdesktop-palette"))) {
Window::Theme::Apply(filePath);
return;
}
}
auto image = Images::Read({
.path = result.paths.isEmpty() ? QString() : result.paths.front(),
.content = result.remoteContent,
.forceOpaque = true,
}).image;
if (image.isNull() || image.width() <= 0 || image.height() <= 0) {
return;
}
auto local = Data::CustomWallPaper();
local.setLocalImageAsThumbnail(std::make_shared<Image>(
std::move(image)));
controller->show(Box<BackgroundPreviewBox>(controller, local));
});
FileDialog::GetOpenPath(
parent.get(),
tr::lng_choose_image(tr::now),
filters.join(u";;"_q),
crl::guard(parent, callback));
}
void SetupSupportSwitchSettings(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
using SwitchType = Support::SwitchSettings;
const auto group = std::make_shared<Ui::RadioenumGroup<SwitchType>>(
controller->session().settings().supportSwitch());
const auto add = [&](SwitchType value, const QString &label) {
container->add(
object_ptr<Ui::Radioenum<SwitchType>>(
container,
group,
value,
label,
st::settingsSendType),
st::settingsSendTypePadding);
};
add(SwitchType::None, "Just send the reply");
add(SwitchType::Next, "Send and switch to next");
add(SwitchType::Previous, "Send and switch to previous");
group->setChangedCallback([=](SwitchType value) {
controller->session().settings().setSupportSwitch(value);
controller->session().saveSettingsDelayed();
});
}
void SetupSupportChatsLimitSlice(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
constexpr auto kDayDuration = 24 * 60 * 60;
struct Option {
int days = 0;
QString label;
};
const auto options = std::vector<Option>{
{ 1, "1 day" },
{ 7, "1 week" },
{ 30, "1 month" },
{ 365, "1 year" },
{ 0, "All of them" },
};
const auto current = controller->session().settings().supportChatsTimeSlice();
const auto days = current / kDayDuration;
const auto best = ranges::min_element(
options,
std::less<>(),
[&](const Option &option) { return std::abs(option.days - days); });
const auto group = std::make_shared<Ui::RadiobuttonGroup>(best->days);
for (const auto &option : options) {
container->add(
object_ptr<Ui::Radiobutton>(
container,
group,
option.days,
option.label,
st::settingsSendType),
st::settingsSendTypePadding);
}
group->setChangedCallback([=](int days) {
controller->session().settings().setSupportChatsTimeSlice(
days * kDayDuration);
controller->session().saveSettingsDelayed();
});
}
void BuildThemeOptionsSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupThemeOptions(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/themes"_q,
.title = tr::lng_settings_themes(tr::now),
.keywords = { u"themes"_q, u"appearance"_q, u"dark"_q, u"light"_q },
.icon = { &st::menuIconPalette },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/themes-edit"_q,
.title = tr::lng_settings_theme_accent_title(tr::now),
.keywords = { u"accent"_q, u"color"_q, u"customize"_q },
};
});
}
void BuildThemeSettingsSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupThemeSettings(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/peer-color"_q,
.title = tr::lng_settings_theme_settings(tr::now),
.keywords = { u"color"_q, u"profile"_q, u"name"_q },
.icon = { &st::menuIconChangeColors },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/auto-night-mode"_q,
.title = tr::lng_settings_auto_night_mode(tr::now),
.keywords = { u"night"_q, u"dark"_q, u"auto"_q, u"system"_q },
.icon = { &st::menuIconNightMode },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/font"_q,
.title = tr::lng_settings_font_family(tr::now),
.keywords = { u"font"_q, u"family"_q, u"text"_q },
.icon = { &st::menuIconFont },
};
});
}
void BuildCloudThemesSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupCloudThemes(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/cloud-themes"_q,
.title = tr::lng_settings_bg_cloud_themes(tr::now),
.keywords = { u"cloud"_q, u"themes"_q, u"online"_q },
};
});
}
void BuildChatBackgroundSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupChatBackground(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/wallpapers"_q,
.title = tr::lng_settings_section_background(tr::now),
.keywords = { u"background"_q, u"wallpaper"_q, u"image"_q },
.icon = { &st::menuIconPhoto },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/wallpapers-set"_q,
.title = tr::lng_settings_bg_from_gallery(tr::now),
.keywords = { u"gallery"_q, u"wallpaper"_q },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/wallpapers-choose-photo"_q,
.title = tr::lng_settings_bg_from_file(tr::now),
.keywords = { u"file"_q, u"photo"_q, u"upload"_q },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/adaptive-layout"_q,
.title = tr::lng_settings_adaptive_wide(tr::now),
.keywords = { u"adaptive"_q, u"wide"_q, u"layout"_q },
.checkIcon = Core::App().settings().adaptiveForWide()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
}
void BuildChatListQuickActionSection(SectionBuilder &builder) {
const auto controller = builder.controller();
builder.add([controller](const WidgetContext &ctx) {
SetupChatListQuickAction(controller, ctx.container.get());
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/quick-dialog-action"_q,
.title = tr::lng_settings_quick_dialog_action_title(tr::now),
.keywords = { u"swipe"_q, u"quick"_q, u"action"_q, u"dialog"_q },
};
});
}
void BuildStickersEmojiSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupStickersEmoji(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/stickers-emoji"_q,
.title = tr::lng_settings_stickers_emoji(tr::now),
.keywords = { u"stickers"_q, u"emoji"_q },
.icon = { &st::menuIconStickers },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/large-emoji"_q,
.title = tr::lng_settings_large_emoji(tr::now),
.keywords = { u"large"_q, u"emoji"_q, u"big"_q },
.checkIcon = Core::App().settings().largeEmoji()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/replace-emoji"_q,
.title = tr::lng_settings_replace_emojis(tr::now),
.keywords = { u"replace"_q, u"emoji"_q, u"convert"_q },
.checkIcon = Core::App().settings().replaceEmoji()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/suggest-emoji"_q,
.title = tr::lng_settings_suggest_emoji(tr::now),
.keywords = { u"suggest"_q, u"emoji"_q, u"autocomplete"_q },
.checkIcon = Core::App().settings().suggestEmoji()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/suggest-animated-emoji"_q,
.title = tr::lng_settings_suggest_animated_emoji(tr::now),
.keywords = { u"animated"_q, u"emoji"_q, u"premium"_q },
.checkIcon = Core::App().settings().suggestAnimatedEmoji()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/suggest-by-emoji"_q,
.title = tr::lng_settings_suggest_by_emoji(tr::now),
.keywords = { u"suggest"_q, u"stickers"_q, u"emoji"_q },
.checkIcon = Core::App().settings().suggestStickersByEmoji()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/loop-stickers"_q,
.title = tr::lng_settings_loop_stickers(tr::now),
.keywords = { u"loop"_q, u"stickers"_q, u"animated"_q },
.checkIcon = Core::App().settings().loopAnimatedStickers()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/my-stickers"_q,
.title = tr::lng_stickers_you_have(tr::now),
.keywords = { u"stickers"_q, u"manage"_q, u"installed"_q },
.icon = { &st::menuIconStickers },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/emoji-sets"_q,
.title = tr::lng_emoji_manage_sets(tr::now),
.keywords = { u"emoji"_q, u"sets"_q, u"manage"_q },
.icon = { &st::menuIconEmoji },
};
});
}
void BuildMessagesSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
SetupMessages(controller, ctx.container.get(), highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/messages"_q,
.title = tr::lng_settings_messages(tr::now),
.keywords = { u"messages"_q, u"send"_q, u"enter"_q },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/send-enter"_q,
.title = tr::lng_settings_send_enter(tr::now),
.keywords = { u"send"_q, u"enter"_q, u"keyboard"_q },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/quick-reaction"_q,
.title = tr::lng_settings_chat_quick_action_react(tr::now),
.keywords = { u"quick"_q, u"reaction"_q, u"double"_q, u"click"_q },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"chat/corner-reaction"_q,
.title = tr::lng_settings_chat_corner_reaction(tr::now),
.keywords = { u"corner"_q, u"reaction"_q },
.checkIcon = Core::App().settings().cornerReaction()
? SearchEntryCheckIcon::Checked
: SearchEntryCheckIcon::Unchecked,
};
});
}
void BuildSensitiveContentSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto highlights = builder.highlights();
builder.add([controller, highlights](const WidgetContext &ctx) {
auto updateOnTick = rpl::single(
) | rpl::then(base::timer_each(60 * crl::time(1000)));
Ui::AddDivider(ctx.container.get());
SetupSensitiveContent(
controller,
ctx.container.get(),
std::move(updateOnTick),
highlights);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"chat/sensitive-content"_q,
.title = tr::lng_settings_sensitive_title(tr::now),
.keywords = { u"sensitive"_q, u"content"_q, u"nsfw"_q, u"adult"_q },
};
});
}
void BuildArchiveSection(SectionBuilder &builder) {
const auto controller = builder.controller();
const auto showOther = builder.showOther();
const auto session = builder.session();
builder.addSkip();
builder.addButton({
.id = u"chat/shortcuts"_q,
.title = tr::lng_settings_shortcuts(),
.icon = { &st::menuIconShortcut },
.onClick = [showOther] { showOther(Shortcuts::Id()); },
.keywords = { u"shortcuts"_q, u"keyboard"_q, u"hotkeys"_q },
});
if (controller) {
PreloadArchiveSettings(session);
}
builder.addButton({
.id = u"chat/archive-settings"_q,
.title = tr::lng_context_archive_settings(),
.icon = { &st::menuIconArchive },
.onClick = [=] {
if (controller) {
controller->show(
Box<Ui::GenericBox>(ArchiveSettingsBox, controller));
}
},
.keywords = { u"archive"_q, u"settings"_q, u"folder"_q },
});
}
void BuildSupportSection(SectionBuilder &builder) {
const auto session = builder.session();
if (!session->supportMode()) {
return;
}
const auto controller = builder.controller();
builder.addSkip();
builder.addSubsectionTitle({
.id = u"chat/support"_q,
.title = rpl::single(u"Support settings"_q),
.keywords = { u"support"_q },
});
builder.addSkip(st::settingsSendTypeSkip);
using SwitchType = Support::SwitchSettings;
builder.add([controller](const WidgetContext &ctx) {
const auto container = ctx.container.get();
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
const auto group = std::make_shared<Ui::RadioenumGroup<SwitchType>>(
controller->session().settings().supportSwitch());
const auto addRadio = [&](SwitchType value, const QString &label) {
inner->add(
object_ptr<Ui::Radioenum<SwitchType>>(
inner,
group,
value,
label,
st::settingsSendType),
st::settingsSendTypePadding);
};
addRadio(SwitchType::None, "Just send the reply");
addRadio(SwitchType::Next, "Send and switch to next");
addRadio(SwitchType::Previous, "Send and switch to previous");
group->setChangedCallback([=](SwitchType value) {
controller->session().settings().setSupportSwitch(value);
controller->session().saveSettingsDelayed();
});
return SectionBuilder::WidgetToAdd{ .widget = std::move(wrap) };
}, [] {
return SearchEntry{
.id = u"chat/support/switch"_q,
.title = u"Send and switch behavior"_q,
.keywords = { u"switch"_q, u"next"_q, u"previous"_q, u"reply"_q },
};
});
builder.addSkip(st::settingsCheckboxesSkip);
const auto templatesAutocomplete = builder.addCheckbox({
.id = u"chat/support/templates"_q,
.title = rpl::single(u"Enable templates autocomplete"_q),
.checked = session->settings().supportTemplatesAutocomplete(),
.keywords = { u"templates"_q, u"autocomplete"_q },
});
if (templatesAutocomplete) {
templatesAutocomplete->checkedChanges(
) | rpl::on_next([=](bool checked) {
controller->session().settings().setSupportTemplatesAutocomplete(
checked);
controller->session().saveSettingsDelayed();
}, templatesAutocomplete->lifetime());
}
const auto allSilent = builder.addCheckbox({
.id = u"chat/support/silent"_q,
.title = rpl::single(u"Send all messages without sound"_q),
.checked = session->settings().supportAllSilent(),
.keywords = { u"silent"_q, u"sound"_q, u"mute"_q },
});
if (allSilent) {
allSilent->checkedChanges(
) | rpl::on_next([=](bool checked) {
controller->session().settings().setSupportAllSilent(checked);
controller->session().saveSettingsDelayed();
}, allSilent->lifetime());
}
builder.addSkip(st::settingsCheckboxesSkip);
builder.addSubsectionTitle({
.id = u"chat/support/chats-period"_q,
.title = rpl::single(u"Load chats for a period"_q),
.keywords = { u"period"_q, u"days"_q },
});
builder.add([controller](const WidgetContext &ctx) {
constexpr auto kDayDuration = 24 * 60 * 60;
struct Option {
int days = 0;
QString label;
};
const auto options = std::vector<Option>{
{ 1, "1 day" },
{ 7, "1 week" },
{ 30, "1 month" },
{ 365, "1 year" },
{ 0, "All of them" },
};
const auto current = controller->session().settings().supportChatsTimeSlice();
const auto days = current / kDayDuration;
const auto best = ranges::min_element(
options,
std::less<>(),
[&](const Option &option) { return std::abs(option.days - days); });
const auto container = ctx.container.get();
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
const auto group = std::make_shared<Ui::RadiobuttonGroup>(best->days);
for (const auto &option : options) {
inner->add(
object_ptr<Ui::Radiobutton>(
inner,
group,
option.days,
option.label,
st::settingsSendType),
st::settingsSendTypePadding);
}
group->setChangedCallback([=](int days) {
controller->session().settings().setSupportChatsTimeSlice(
days * kDayDuration);
controller->session().saveSettingsDelayed();
});
return SectionBuilder::WidgetToAdd{ .widget = std::move(wrap) };
}, [] {
return SearchEntry{
.id = u"chat/support/chats-period/options"_q,
.title = u"Chat loading period options"_q,
.keywords = { u"week"_q, u"month"_q, u"year"_q },
};
});
builder.addSkip(st::settingsCheckboxesSkip);
builder.addSkip();
}
void BuildChatSectionContent(SectionBuilder &builder) {
BuildThemeOptionsSection(builder);
BuildThemeSettingsSection(builder);
BuildCloudThemesSection(builder);
BuildChatBackgroundSection(builder);
BuildChatListQuickActionSection(builder);
BuildStickersEmojiSection(builder);
BuildMessagesSection(builder);
BuildSensitiveContentSection(builder);
BuildArchiveSection(builder);
BuildSupportSection(builder);
}
const auto kMeta = BuildHelper({
.id = Chat::Id(),
.parentId = Main::Id(),
.title = &tr::lng_settings_section_chat_settings,
.icon = &st::menuIconChatBubble,
}, [](SectionBuilder &builder) {
BuildChatSectionContent(builder);
});
} // namespace
void PaintRoundColorButton(
QPainter &p,
int size,
QBrush brush,
float64 selected) {
const auto rect = QRect(0, 0, size, size);
p.setBrush(brush);
p.setPen(Qt::NoPen);
p.drawEllipse(rect);
if (selected > 0.) {
const auto startSkip = -st::settingsAccentColorLine / 2.;
const auto endSkip = float64(st::settingsAccentColorSkip);
const auto skip = startSkip + (endSkip - startSkip) * selected;
auto pen = st::boxBg->p;
pen.setWidth(st::settingsAccentColorLine);
p.setBrush(Qt::NoBrush);
p.setPen(pen);
p.setOpacity(selected);
p.drawEllipse(QRectF(rect).marginsRemoved({ skip, skip, skip, skip }));
}
}
void SetupStickersEmoji(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
Ui::AddSkip(container);
const auto title = Ui::AddSubsectionTitle(
container,
tr::lng_settings_stickers_emoji());
if (highlights) {
highlights->push_back({ u"chat/stickers-emoji"_q, {
title.get(),
SubsectionTitleHighlight(),
} });
}
const auto session = &controller->session();
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
container->add(object_ptr<Ui::OverrideMargins>(
container,
std::move(wrap),
QMargins(0, 0, 0, st::settingsCheckbox.margin.bottom())));
const auto checkbox = [&](const QString &label, bool checked) {
return object_ptr<Ui::Checkbox>(
container,
label,
checked,
st::settingsCheckbox);
};
const auto add = [&](const QString &label, bool checked, auto &&handle) {
return inner->add(
checkbox(label, checked),
st::settingsCheckboxPadding
)->checkedChanges(
) | rpl::on_next(
std::move(handle),
inner->lifetime());
};
const auto addWithReturn = [&](
const QString &label,
bool checked,
auto &&handle) {
const auto result = inner->add(
checkbox(label, checked),
st::settingsCheckboxPadding);
result->checkedChanges(
) | rpl::on_next(
std::move(handle),
inner->lifetime());
return result;
};
const auto addSliding = [&](
const QString &label,
bool checked,
auto &&handle,
rpl::producer<bool> shown) {
const auto wrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
checkbox(label, checked),
st::settingsCheckboxPadding));
wrap->setDuration(0)->toggleOn(std::move(shown))->entity()->checkedChanges(
) | rpl::on_next(
std::move(handle),
inner->lifetime());
return wrap->entity();
};
const auto largeEmoji = addWithReturn(
tr::lng_settings_large_emoji(tr::now),
Core::App().settings().largeEmoji(),
[=](bool checked) {
Core::App().settings().setLargeEmoji(checked);
Core::App().saveSettingsDelayed();
});
if (highlights) {
highlights->push_back({ u"chat/large-emoji"_q, {
largeEmoji,
{ .radius = st::boxRadius }
} });
}
add(
tr::lng_settings_replace_emojis(tr::now),
Core::App().settings().replaceEmoji(),
[=](bool checked) {
Core::App().settings().setReplaceEmoji(checked);
Core::App().saveSettingsDelayed();
});
const auto suggestEmoji = inner->lifetime().make_state<
rpl::variable<bool>
>(Core::App().settings().suggestEmoji());
add(
tr::lng_settings_suggest_emoji(tr::now),
Core::App().settings().suggestEmoji(),
[=](bool checked) {
*suggestEmoji = checked;
Core::App().settings().setSuggestEmoji(checked);
Core::App().saveSettingsDelayed();
});
using namespace rpl::mappers;
const auto suggestAnimated = addSliding(
tr::lng_settings_suggest_animated_emoji(tr::now),
Core::App().settings().suggestAnimatedEmoji(),
[=](bool checked) {
Core::App().settings().setSuggestAnimatedEmoji(checked);
Core::App().saveSettingsDelayed();
},
rpl::combine(
Data::AmPremiumValue(session),
suggestEmoji->value(),
_1 && _2));
if (highlights) {
highlights->push_back({ u"chat/suggest-animated-emoji"_q, {
suggestAnimated,
{ .radius = st::boxRadius }
} });
}
const auto suggestByEmoji = addWithReturn(
tr::lng_settings_suggest_by_emoji(tr::now),
Core::App().settings().suggestStickersByEmoji(),
[=](bool checked) {
Core::App().settings().setSuggestStickersByEmoji(checked);
Core::App().saveSettingsDelayed();
});
if (highlights) {
highlights->push_back({ u"chat/suggest-by-emoji"_q, {
suggestByEmoji,
{ .radius = st::boxRadius },
} });
}
add(
tr::lng_settings_loop_stickers(tr::now),
Core::App().settings().loopAnimatedStickers(),
[=](bool checked) {
Core::App().settings().setLoopAnimatedStickers(checked);
Core::App().saveSettingsDelayed();
});
AddButtonWithIcon(
container,
tr::lng_stickers_you_have(),
st::settingsButton,
{ &st::menuIconStickers }
)->addClickHandler([=] {
controller->show(Box<StickersBox>(
controller->uiShow(),
StickersBox::Section::Installed));
});
AddButtonWithIcon(
container,
tr::lng_emoji_manage_sets(),
st::settingsButton,
{ &st::menuIconEmoji }
)->addClickHandler([=] {
controller->show(Box<Ui::Emoji::ManageSetsBox>(session));
});
Ui::AddSkip(container, st::settingsCheckboxesSkip);
}
void SetupMessages(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
Ui::AddDivider(container);
Ui::AddSkip(container);
Ui::AddSubsectionTitle(container, tr::lng_settings_messages());
Ui::AddSkip(container, st::settingsSendTypeSkip);
using SendByType = Ui::InputSubmitSettings;
using Quick = HistoryView::DoubleClickQuickAction;
const auto skip = st::settingsSendTypeSkip;
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
container->add(
object_ptr<Ui::OverrideMargins>(
container,
std::move(wrap),
QMargins(0, skip, 0, skip)));
const auto groupSend = std::make_shared<Ui::RadioenumGroup<SendByType>>(
Core::App().settings().sendSubmitWay());
const auto addSend = [&](SendByType value, const QString &text) {
inner->add(
object_ptr<Ui::Radioenum<SendByType>>(
inner,
groupSend,
value,
text,
st::settingsSendType),
st::settingsSendTypePadding);
};
addSend(SendByType::Enter, tr::lng_settings_send_enter(tr::now));
addSend(
SendByType::CtrlEnter,
(Platform::IsMac()
? tr::lng_settings_send_cmdenter(tr::now)
: tr::lng_settings_send_ctrlenter(tr::now)));
groupSend->setChangedCallback([=](SendByType value) {
Core::App().settings().setSendSubmitWay(value);
Core::App().saveSettingsDelayed();
});
Ui::AddSkip(inner, st::settingsCheckboxesSkip);
const auto groupQuick = std::make_shared<Ui::RadioenumGroup<Quick>>(
Core::App().settings().chatQuickAction());
const auto addQuick = [&](Quick value, const QString &text) {
return inner->add(
object_ptr<Ui::Radioenum<Quick>>(
inner,
groupQuick,
value,
text,
st::settingsSendType),
st::settingsSendTypePadding);
};
addQuick(Quick::Reply, tr::lng_settings_chat_quick_action_reply(tr::now));
const auto react = addQuick(
Quick::React,
tr::lng_settings_chat_quick_action_react(tr::now));
if (highlights) {
highlights->push_back({ u"chat/quick-reaction"_q, {
react,
{ .radius = st::boxRadius },
} });
}
const auto buttonRight = Ui::CreateSimpleCircleButton(
inner,
st::stickersRemove.ripple);
buttonRight->resize(st::stickersRemove.width, st::stickersRemove.height);
const auto toggleButtonRight = [=](bool value) {
buttonRight->setAttribute(Qt::WA_TransparentForMouseEvents, !value);
};
toggleButtonRight(false);
struct State {
struct {
std::vector<rpl::lifetime> lifetimes;
bool flag = false;
} icons;
};
const auto state = buttonRight->lifetime().make_state<State>();
state->icons.lifetimes = std::vector<rpl::lifetime>(2);
const auto &reactions = controller->session().data().reactions();
auto idValue = rpl::single(
reactions.favoriteId()
) | rpl::then(
reactions.favoriteUpdates() | rpl::map([=] {
return controller->session().data().reactions().favoriteId();
})
) | rpl::filter([](const Data::ReactionId &id) {
return !id.empty();
});
auto selected = rpl::duplicate(idValue);
std::move(
selected
) | rpl::on_next([=, idValue = std::move(idValue)](
const Data::ReactionId &id) {
const auto index = state->icons.flag ? 1 : 0;
const auto iconSize = st::settingsReactionRightIcon;
const auto &reactions = controller->session().data().reactions();
const auto &list = reactions.list(Data::Reactions::Type::All);
const auto i = ranges::find(list, id, &Data::Reaction::id);
state->icons.lifetimes[index] = rpl::lifetime();
if (i != end(list)) {
AddReactionAnimatedIcon(
inner,
buttonRight->geometryValue(
) | rpl::map([=](const QRect &r) {
return QPoint(
r.left() + (r.width() - iconSize) / 2,
r.top() + (r.height() - iconSize) / 2);
}),
iconSize,
*i,
buttonRight->events(
) | rpl::filter([=](not_null<QEvent*> event) {
return event->type() == QEvent::Enter;
}) | rpl::to_empty,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]);
} else if (const auto customId = id.custom()) {
AddReactionCustomIcon(
inner,
buttonRight->geometryValue(
) | rpl::map([=](const QRect &r) {
return QPoint(
r.left() + (r.width() - iconSize) / 2,
r.top() + (r.height() - iconSize) / 2);
}),
iconSize,
controller,
customId,
rpl::duplicate(idValue) | rpl::skip(1) | rpl::to_empty,
&state->icons.lifetimes[index]);
}
state->icons.flag = !state->icons.flag;
toggleButtonRight(true);
}, buttonRight->lifetime());
react->geometryValue(
) | rpl::on_next([=](const QRect &r) {
const auto rightSize = buttonRight->size();
buttonRight->moveToRight(
st::settingsButtonRightSkip,
r.y() + (r.height() - rightSize.height()) / 2);
}, buttonRight->lifetime());
groupQuick->setChangedCallback([=](Quick value) {
Core::App().settings().setChatQuickAction(value);
Core::App().saveSettingsDelayed();
});
buttonRight->setClickedCallback([=, show = controller->uiShow()] {
show->showBox(Box(ReactionsSettingsBox, controller));
});
if (highlights) {
highlights->push_back({ u"chat/quick-reaction-choose"_q, {
buttonRight.get(),
{ .shape = HighlightShape::Ellipse },
} });
}
Ui::AddSkip(inner, st::settingsSendTypeSkip);
inner->add(
object_ptr<Ui::Checkbox>(
inner,
tr::lng_settings_chat_corner_reaction(tr::now),
Core::App().settings().cornerReaction(),
st::settingsCheckbox),
st::settingsCheckboxPadding
)->checkedChanges(
) | rpl::on_next([=](bool checked) {
Core::App().settings().setCornerReaction(checked);
Core::App().saveSettingsDelayed();
}, inner->lifetime());
Ui::AddSkip(inner);
}
void SetupArchive(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
Ui::AddSkip(container);
AddButtonWithIcon(
container,
tr::lng_settings_shortcuts(),
st::settingsButton,
{ &st::menuIconShortcut }
)->addClickHandler([=] {
showOther(Shortcuts::Id());
});
PreloadArchiveSettings(&controller->session());
AddButtonWithIcon(
container,
tr::lng_context_archive_settings(),
st::settingsButton,
{ &st::menuIconArchive }
)->addClickHandler([=] {
controller->show(Box(Settings::ArchiveSettingsBox, controller));
});
}
void SetupExport(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
AddButtonWithIcon(
container,
tr::lng_settings_export_data(),
st::settingsButton,
{ &st::menuIconExport }
)->addClickHandler([=] {
const auto session = &controller->session();
controller->window().hideSettingsAndLayer();
base::call_delayed(
st::boxDuration,
session,
[=] { Core::App().exportManager().start(session); });
});
AddButtonWithIcon(
container,
tr::lng_settings_experimental(),
st::settingsButton,
{ &st::menuIconExperimental }
)->addClickHandler([=] {
showOther(Experimental::Id());
});
}
void SetupLocalStorage(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
AddButtonWithIcon(
container,
tr::lng_settings_manage_local_storage(),
st::settingsButton,
{ &st::menuIconStorage }
)->addClickHandler([=] { LocalStorageBox::Show(controller); });
}
void SetupDataStorage(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
using namespace rpl::mappers;
Ui::AddSkip(container);
Ui::AddSubsectionTitle(container, tr::lng_settings_data_storage());
SetupConnectionType(
&controller->window(),
&controller->session().account(),
container);
#ifndef OS_WIN_STORE
const auto showpath = container->lifetime(
).make_state<rpl::event_stream<bool>>();
const auto path = container->add(
object_ptr<Ui::SlideWrap<Button>>(
container,
CreateButtonWithIcon(
container,
tr::lng_download_path(),
st::settingsButton,
{ &st::menuIconShowInFolder })));
auto pathtext = Core::App().settings().downloadPathValue(
) | rpl::map([](const QString &text) {
if (text.isEmpty()) {
return Core::App().canReadDefaultDownloadPath()
? tr::lng_download_path_default(tr::now)
: tr::lng_download_path_temp(tr::now);
} else if (text == FileDialog::Tmp()) {
return tr::lng_download_path_temp(tr::now);
}
return QDir::toNativeSeparators(text);
});
CreateRightLabel(
path->entity(),
std::move(pathtext),
st::settingsButton,
tr::lng_download_path());
path->entity()->addClickHandler([=] {
controller->show(Box<DownloadPathBox>(controller));
});
#endif // OS_WIN_STORE
SetupLocalStorage(controller, container);
AddButtonWithIcon(
container,
tr::lng_downloads_section(),
st::settingsButton,
{ &st::menuIconDownload }
)->setClickedCallback([=] {
controller->showSection(
Info::Downloads::Make(controller->session().user()));
});
const auto ask = container->add(object_ptr<Ui::SettingsButton>(
container,
tr::lng_download_path_ask(),
st::settingsButtonNoIcon
))->toggleOn(rpl::single(Core::App().settings().askDownloadPath()));
ask->toggledValue(
) | rpl::filter([](bool checked) {
return (checked != Core::App().settings().askDownloadPath());
}) | rpl::on_next([=](bool checked) {
Core::App().settings().setAskDownloadPath(checked);
Core::App().saveSettingsDelayed();
#ifndef OS_WIN_STORE
showpath->fire_copy(!checked);
#endif // OS_WIN_STORE
}, ask->lifetime());
#ifndef OS_WIN_STORE
path->toggleOn(ask->toggledValue() | rpl::map(!_1));
#endif // OS_WIN_STORE
Ui::AddSkip(container, st::settingsCheckboxesSkip);
}
void SetupAutoDownload(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container);
Ui::AddSkip(container);
Ui::AddSubsectionTitle(container, tr::lng_media_auto_settings());
using Source = Data::AutoDownload::Source;
const auto add = [&](
rpl::producer<QString> label,
Source source,
IconDescriptor &&descriptor) {
AddButtonWithIcon(
container,
std::move(label),
st::settingsButton,
std::move(descriptor)
)->addClickHandler([=] {
controller->show(
Box<AutoDownloadBox>(&controller->session(), source));
});
};
add(
tr::lng_media_auto_in_private(),
Source::User,
{ &st::menuIconProfile });
add(
tr::lng_media_auto_in_groups(),
Source::Group,
{ &st::menuIconGroups });
add(
tr::lng_media_auto_in_channels(),
Source::Channel,
{ &st::menuIconChannel });
Ui::AddSkip(container, st::settingsCheckboxesSkip);
}
void SetupChatBackground(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
Ui::AddDivider(container);
Ui::AddSkip(container);
const auto title = Ui::AddSubsectionTitle(
container,
tr::lng_settings_section_background());
const auto row = container->add(
object_ptr<BackgroundRow>(container, controller),
st::settingsBackgroundPadding);
if (highlights) {
highlights->push_back({ u"chat/wallpapers"_q, {
title.get(),
SubsectionTitleHighlight(),
} });
highlights->push_back({ u"chat/wallpapers-set"_q, {
row->chooseFromGallery(),
SubsectionTitleHighlight(),
} });
highlights->push_back({ u"chat/wallpapers-choose-photo"_q, {
row->chooseFromFile(),
SubsectionTitleHighlight(),
} });
}
const auto skipTop = st::settingsCheckbox.margin.top();
const auto skipBottom = st::settingsCheckbox.margin.bottom();
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
container->add(
object_ptr<Ui::OverrideMargins>(
container,
std::move(wrap),
QMargins(0, skipTop, 0, skipBottom)));
Ui::AddSkip(container, st::settingsTileSkip);
const auto background = Window::Theme::Background();
const auto tile = inner->add(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
object_ptr<Ui::Checkbox>(
inner,
tr::lng_settings_bg_tile(tr::now),
background->tile(),
st::settingsCheckbox),
st::settingsSendTypePadding));
const auto adaptive = inner->add(
object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
inner,
object_ptr<Ui::Checkbox>(
inner,
tr::lng_settings_adaptive_wide(tr::now),
Core::App().settings().adaptiveForWide(),
st::settingsCheckbox),
st::settingsSendTypePadding));
tile->entity()->checkedChanges(
) | rpl::on_next([=](bool checked) {
background->setTile(checked);
}, tile->lifetime());
const auto shown = [=] {
return !background->paper().isPattern()
&& !background->colorForFill();
};
tile->toggle(shown(), anim::type::instant);
using Update = const Window::Theme::BackgroundUpdate;
background->updates(
) | rpl::filter([](const Update &update) {
return (update.type == Update::Type::Changed)
|| (update.type == Update::Type::New);
}) | rpl::on_next([=] {
tile->entity()->setChecked(background->tile());
tile->toggle(shown(), anim::type::instant);
}, tile->lifetime());
adaptive->toggleOn(controller->adaptive().chatLayoutValue(
) | rpl::map([](Window::Adaptive::ChatLayout layout) {
return (layout == Window::Adaptive::ChatLayout::Wide);
}));
adaptive->entity()->checkedChanges(
) | rpl::on_next([=](bool checked) {
Core::App().settings().setAdaptiveForWide(checked);
Core::App().saveSettingsDelayed();
}, adaptive->lifetime());
}
void SetupChatListQuickAction(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
Ui::AddDivider(container);
Ui::AddSkip(container);
Ui::AddSubsectionTitle(
container,
tr::lng_settings_quick_dialog_action_title());
using Type = Dialogs::Ui::QuickDialogAction;
using LabelType = Dialogs::Ui::QuickDialogActionLabel;
const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(
Core::App().settings().quickDialogAction());
group->setChangedCallback([=](Type value) {
Core::App().settings().setQuickDialogAction(value);
Core::App().saveSettings();
});
const auto actionToLabel = [](Type value) {
switch (value) {
case Type::Mute: return LabelType::Mute;
case Type::Pin: return LabelType::Pin;
case Type::Read: return LabelType::Read;
case Type::Archive: return LabelType::Archive;
case Type::Delete: return LabelType::Delete;
default: return LabelType::Disabled;
}
};
static constexpr auto kDisabledIconRatio = 1.25;
const auto addPreview = [=](not_null<Ui::VerticalLayout*> container) {
const auto widget = container->add(
object_ptr<Ui::RpWidget>(container));
widget->resize(0, st::dialogsRowHeight);
struct State {
std::unique_ptr<Lottie::Icon> icon;
};
const auto state = widget->lifetime().make_state<State>();
group->value() | rpl::on_next([=](Type value) {
const auto label = actionToLabel(value);
state->icon = Lottie::MakeIcon({
.name = Dialogs::ResolveQuickDialogLottieIconName(label),
.sizeOverride = Size((label == LabelType::Disabled)
? int(st::dialogsQuickActionSize * kDisabledIconRatio)
: st::dialogsQuickActionSize),
});
state->icon->animate(
[=] { widget->update(); },
0,
state->icon->framesCount() - 1);
widget->update();
}, widget->lifetime());
widget->paintRequest() | rpl::on_next([=] {
auto p = QPainter(widget);
auto hq = PainterHighQualityEnabler(p);
const auto height = st::dialogsRowHeight;
const auto actionWidth = st::dialogsQuickActionRippleSize * 0.75;
const auto rightOffset = st::dialogsQuickActionRippleSize
+ st::dialogsQuickActionSize;
const auto rect = QRect(
widget->width()
- actionWidth
- st::boxRowPadding.right()
- rightOffset,
0,
actionWidth,
height);
auto path = QPainterPath();
path.addRoundedRect(
QRect(
-actionWidth,
0,
rect::right(rect) + actionWidth,
height),
st::roundRadiusLarge,
st::roundRadiusLarge);
p.setClipPath(path);
const auto label = actionToLabel(group->current());
const auto isDisabled = (label == LabelType::Disabled);
p.fillRect(
QRect(0, 0, rect::right(rect), st::lineWidth),
st::windowBgOver);
p.fillRect(
QRect(
0,
rect::bottom(rect) - st::lineWidth,
rect::right(rect),
st::lineWidth),
st::windowBgOver);
p.fillRect(rect, Dialogs::ResolveQuickActionBg(label));
if (state->icon) {
Dialogs::DrawQuickAction(
p,
rect,
state->icon.get(),
label,
isDisabled ? kDisabledIconRatio : 1.,
isDisabled);
}
p.translate(-height / 2, 0);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgOver);
p.drawEllipse(Rect(Size(height)) - Margins(height / 6));
const auto h = st::normalFont->ascent / 1.5;
p.drawRoundedRect(
height,
height / 2 - h * 1.5,
st::dialogsQuickActionRippleSize * 0.6,
h,
h / 2,
h / 2);
p.drawRoundedRect(
height,
height / 2 + h,
st::dialogsQuickActionRippleSize * 1.0,
h,
h / 2,
h / 2);
p.setClipping(false);
p.resetTransform();
p.setFont(st::settingsQuickDialogActionsTriggerFont);
p.setPen(st::windowSubTextFg);
p.drawText(
QRect(
widget->width()
- st::dialogsQuickActionRippleSize
- st::boxRowPadding.right(),
0,
st::dialogsQuickActionRippleSize,
height),
isDisabled
? tr::lng_settings_quick_dialog_action_swipe(tr::now)
: tr::lng_settings_quick_dialog_action_both(tr::now),
style::al_center);
}, widget->lifetime());
};
const auto &st = st::settingsButton;
const auto button = container->add(
object_ptr<Ui::SettingsButton>(
container,
group->value() | rpl::map([](Type value) {
return ((value == Dialogs::Ui::QuickDialogAction::Mute)
? tr::lng_settings_quick_dialog_action_mute
: (value == Dialogs::Ui::QuickDialogAction::Pin)
? tr::lng_settings_quick_dialog_action_pin
: (value == Dialogs::Ui::QuickDialogAction::Read)
? tr::lng_settings_quick_dialog_action_read
: (value == Dialogs::Ui::QuickDialogAction::Archive)
? tr::lng_settings_quick_dialog_action_archive
: tr::lng_settings_quick_dialog_action_disabled)();
}) | rpl::flatten_latest(),
st));
{
const auto icon = button->lifetime().make_state<Ui::RpWidget>(button);
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
icon->resize(st::menuIconArchive.size());
icon->show();
button->sizeValue(
) | rpl::on_next([=, left = st.iconLeft](QSize size) {
icon->moveToLeft(
left,
(size.height() - icon->height()) / 2,
size.width());
}, icon->lifetime());
icon->paintRequest(
) | rpl::on_next([=] {
auto p = QPainter(icon);
const auto value = group->current();
((value == Dialogs::Ui::QuickDialogAction::Mute)
? st::menuIconMute
: (value == Dialogs::Ui::QuickDialogAction::Pin)
? st::menuIconPin
: (value == Dialogs::Ui::QuickDialogAction::Read)
? st::menuIconMarkRead
: (value == Dialogs::Ui::QuickDialogAction::Delete)
? st::menuIconDelete
: (value == Dialogs::Ui::QuickDialogAction::Archive)
? st::menuIconArchive
: st::menuIconShowInFolder).paintInCenter(p, icon->rect());
}, icon->lifetime());
}
button->setClickedCallback([=] {
controller->uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_settings_quick_dialog_action_title());
const auto addRadio = [&](Type value, tr::phrase<> phrase) {
box->verticalLayout()->add(
object_ptr<Ui::Radioenum<Type>>(
box->verticalLayout(),
group,
value,
phrase(tr::now),
st::settingsSendType),
st::settingsSendTypePadding);
};
addPreview(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
addRadio(Type::Mute, tr::lng_settings_quick_dialog_action_mute);
addRadio(Type::Pin, tr::lng_settings_quick_dialog_action_pin);
addRadio(Type::Read, tr::lng_settings_quick_dialog_action_read);
addRadio(
Type::Archive,
tr::lng_settings_quick_dialog_action_archive);
addRadio(
Type::Delete,
tr::lng_settings_quick_dialog_action_delete);
addRadio(
Type::Disabled,
tr::lng_settings_quick_dialog_action_disabled);
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
}));
});
Ui::AddSkip(container);
Ui::AddDividerText(
container,
tr::lng_settings_quick_dialog_action_about());
Ui::AddSkip(container);
}
void SetupDefaultThemes(
not_null<Window::Controller*> window,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
using Type = Window::Theme::EmbeddedType;
using Scheme = Window::Theme::EmbeddedScheme;
using Check = Window::Theme::CloudListCheck;
using namespace Window::Theme;
const auto block = container->add(object_ptr<Ui::FixedHeightWidget>(
container));
const auto palette = Ui::CreateChild<ColorsPalette>(
container.get(),
container.get());
const auto chosen = [] {
const auto &object = Background()->themeObject();
if (object.cloud.id) {
return Type(-1);
}
for (const auto &scheme : kSchemesList) {
if (object.pathAbsolute == scheme.path) {
return scheme.type;
}
}
return Type(-1);
};
const auto group = std::make_shared<Ui::RadioenumGroup<Type>>(chosen());
const auto apply = [=](const Scheme &scheme) {
const auto isNight = [](const Scheme &scheme) {
const auto type = scheme.type;
return (type != Type::DayBlue) && (type != Type::Default);
};
const auto currentlyIsCustom = (chosen() == Type(-1))
&& !Background()->themeObject().cloud.id;
const auto keep = [=] {
if (!currentlyIsCustom) {
KeepApplied();
}
};
if (IsNightMode() == isNight(scheme)) {
ApplyDefaultWithPath(scheme.path);
keep();
} else {
Window::Theme::ToggleNightModeWithConfirmation(
window,
[=, path = scheme.path] { ToggleNightMode(path); keep();});
}
};
const auto schemeClicked = [=](
const Scheme &scheme,
Qt::KeyboardModifiers modifiers) {
apply(scheme);
};
auto checks = base::flat_map<Type,not_null<Check*>>();
auto buttons = ranges::views::all(
kSchemesList
) | ranges::views::transform([&](const Scheme &scheme) {
auto check = std::make_unique<Check>(
ColorsFromScheme(scheme),
false);
const auto weak = check.get();
const auto result = Ui::CreateChild<Ui::Radioenum<Type>>(
block,
group,
scheme.type,
QString(),
st::settingsTheme,
std::move(check));
rpl::duplicate(
scheme.name
) | rpl::on_next([=](const QString &themeName) {
result->setText(themeName);
}, result->lifetime());
result->addClickHandler([=] {
schemeClicked(scheme, result->clickModifiers());
});
weak->setUpdateCallback([=] { result->update(); });
checks.emplace(scheme.type, weak);
return result;
}) | ranges::to_vector;
const auto refreshColorizer = [=](Type type) {
if (type == chosen()) {
palette->show(type);
}
const auto &colors = Core::App().settings().themesAccentColors();
const auto i = checks.find(type);
const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
if (scheme == end(kSchemesList)) {
return;
}
if (i != end(checks)) {
if (const auto color = colors.get(type)) {
const auto colorizer = ColorizerFrom(*scheme, *color);
i->second->setColors(ColorsFromScheme(*scheme, colorizer));
} else {
i->second->setColors(ColorsFromScheme(*scheme));
}
}
};
group->setChangedCallback([=](Type type) {
const auto scheme = ranges::find(
kSchemesList,
type,
&Scheme::type);
if (scheme != end(kSchemesList)) {
apply(*scheme);
} else {
group->setValue(chosen());
}
});
for (const auto &scheme : kSchemesList) {
refreshColorizer(scheme.type);
}
if (highlights) {
const auto add = st::roundRadiusSmall;
highlights->push_back({ u"chat/themes-edit"_q, {
palette->editButton(),
{
.margin = { -add, -add, -add, -add },
.shape = HighlightShape::Ellipse,
},
} });
}
Background()->updates(
) | rpl::filter([](const BackgroundUpdate &update) {
return (update.type == BackgroundUpdate::Type::ApplyingTheme);
}) | rpl::map([=] {
return chosen();
}) | rpl::on_next([=](Type type) {
refreshColorizer(type);
group->setValue(type);
}, container->lifetime());
for (const auto button : buttons) {
button->setCheckAlignment(style::al_top);
button->resizeToWidth(button->width());
}
block->resize(block->width(), buttons[0]->height());
block->widthValue(
) | rpl::on_next([buttons = std::move(buttons)](int width) {
Expects(!buttons.empty());
const auto padding = st::settingsButtonNoIcon.padding;
width -= padding.left() + padding.right();
const auto desired = st::settingsThemePreviewSize.width();
const auto count = int(buttons.size());
const auto skips = count - 1;
const auto minSkip = st::settingsThemeMinSkip;
const auto single = [&] {
if (width >= skips * minSkip + count * desired) {
return desired;
}
return (width - skips * minSkip) / count;
}();
if (single <= 0) {
return;
}
const auto fullSkips = width - count * single;
const auto skip = fullSkips / float64(skips);
auto left = padding.left() + 0.;
for (const auto button : buttons) {
button->resizeToWidth(single);
button->moveToLeft(int(base::SafeRound(left)), 0);
left += button->width() + skip;
}
}, block->lifetime());
palette->selected(
) | rpl::on_next([=](QColor color) {
if (Background()->editingTheme()) {
window->show(Ui::MakeInformBox(
tr::lng_theme_editor_cant_change_theme()));
return;
}
const auto type = chosen();
const auto scheme = ranges::find(kSchemesList, type, &Scheme::type);
if (scheme == end(kSchemesList)) {
return;
}
auto &colors = Core::App().settings().themesAccentColors();
if (colors.get(type) != color) {
colors.set(type, color);
Local::writeSettings();
}
apply(*scheme);
}, container->lifetime());
Ui::AddSkip(container);
}
void SetupThemeOptions(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
using namespace Window::Theme;
Ui::AddSkip(container, st::settingsPrivacySkip);
const auto title = Ui::AddSubsectionTitle(
container,
tr::lng_settings_themes());
if (highlights) {
highlights->push_back({ u"chat/themes"_q, {
title.get(),
SubsectionTitleHighlight(),
} });
}
Ui::AddSkip(container, st::settingsThemesTopSkip);
SetupDefaultThemes(&controller->window(), container, highlights);
Ui::AddSkip(container);
}
void SetupCloudThemes(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
using namespace Window::Theme;
using namespace rpl::mappers;
const auto wrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container))
)->setDuration(0);
const auto inner = wrap->entity();
Ui::AddDivider(inner);
Ui::AddSkip(inner, st::settingsPrivacySkip);
const auto title = AddSubsectionTitle(
inner,
tr::lng_settings_bg_cloud_themes());
const auto showAll = Ui::CreateChild<Ui::LinkButton>(
inner,
tr::lng_settings_bg_show_all(tr::now));
rpl::combine(
title->topValue(),
inner->widthValue(),
showAll->widthValue()
) | rpl::on_next([=](int top, int outerWidth, int width) {
showAll->moveToRight(
st::defaultSubsectionTitlePadding.left(),
top,
outerWidth);
}, showAll->lifetime());
Ui::AddSkip(inner, st::settingsThemesTopSkip);
const auto list = inner->lifetime().make_state<CloudList>(
inner,
controller);
inner->add(
list->takeWidget(),
style::margins(
st::settingsButtonNoIcon.padding.left(),
0,
st::settingsButtonNoIcon.padding.right(),
0));
list->allShown(
) | rpl::on_next([=](bool shown) {
showAll->setVisible(!shown);
}, showAll->lifetime());
showAll->addClickHandler([=] {
list->showAll();
});
const auto editWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner))
)->setDuration(0);
const auto edit = editWrap->entity();
Ui::AddSkip(edit, st::settingsThemesBottomSkip);
AddButtonWithIcon(
edit,
tr::lng_settings_bg_theme_edit(),
st::settingsButton,
{ &st::menuIconPalette }
)->addClickHandler([=] {
StartEditor(
&controller->window(),
Background()->themeObject().cloud);
});
editWrap->toggleOn(rpl::single(BackgroundUpdate(
BackgroundUpdate::Type::ApplyingTheme,
Background()->tile()
)) | rpl::then(
Background()->updates()
) | rpl::filter([](const BackgroundUpdate &update) {
return (update.type == BackgroundUpdate::Type::ApplyingTheme);
}) | rpl::map([=] {
const auto userId = controller->session().userId();
return (Background()->themeObject().cloud.createdBy == userId);
}));
Ui::AddSkip(inner, 2 * st::defaultVerticalListSkip);
wrap->setDuration(0)->toggleOn(list->empty() | rpl::map(!_1));
}
void SetupThemeSettings(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
HighlightRegistry *highlights) {
Ui::AddDivider(container);
Ui::AddSkip(container, st::settingsPrivacySkip);
Ui::AddSubsectionTitle(container, tr::lng_settings_theme_settings());
AddPeerColorButton(
container,
controller->uiShow(),
controller->session().user(),
st::settingsColorButton);
const auto settings = &Core::App().settings();
if (settings->systemDarkMode().has_value()) {
auto label = settings->systemDarkModeEnabledValue(
) | rpl::map([=](bool enabled) {
return enabled
? tr::lng_settings_auto_night_mode_on()
: tr::lng_settings_auto_night_mode_off();
}) | rpl::flatten_latest();
const auto button = AddButtonWithLabel(
container,
tr::lng_settings_auto_night_mode(),
std::move(label),
st::settingsButton,
{ &st::menuIconNightMode });
button->setClickedCallback([=] {
const auto now = !settings->systemDarkModeEnabled();
if (now && Window::Theme::Background()->editingTheme()) {
controller->show(Ui::MakeInformBox(
tr::lng_theme_editor_cant_change_theme()));
} else {
settings->setSystemDarkModeEnabled(now);
Core::App().saveSettingsDelayed();
}
});
if (highlights) {
highlights->push_back({ u"chat/auto-night-mode"_q, {
button.get()
} });
}
}
const auto family = container->lifetime().make_state<
rpl::variable<QString>
>(settings->customFontFamily());
auto label = family->value() | rpl::map([](QString family) {
return family.isEmpty()
? tr::lng_font_default(tr::now)
: (family == style::SystemFontTag())
? tr::lng_font_system(tr::now)
: family;
});
AddButtonWithLabel(
container,
tr::lng_settings_font_family(),
std::move(label),
st::settingsButton,
{ &st::menuIconFont }
)->setClickedCallback([=] {
const auto save = [=](QString chosen) {
*family = chosen;
settings->setCustomFontFamily(chosen);
Local::writeSettings();
Core::Restart();
};
const auto theme = std::shared_ptr<Ui::ChatTheme>(
Window::Theme::DefaultChatThemeOn(container->lifetime()));
const auto generateBg = [=] {
const auto size = st::boxWidth;
const auto ratio = style::DevicePixelRatio();
auto result = QImage(
QSize(size, size) * ratio,
QImage::Format_ARGB32_Premultiplied);
auto p = QPainter(&result);
Window::SectionWidget::PaintBackground(
p,
theme.get(),
QSize(size, size * 3),
QRect(0, 0, size, size));
p.end();
return result;
};
controller->show(
Box(Ui::ChooseFontBox, generateBg, family->current(), save));
});
Ui::AddSkip(container, st::settingsCheckboxesSkip);
}
void SetupSupport(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
Ui::AddSkip(container);
Ui::AddSubsectionTitle(container, rpl::single(u"Support settings"_q));
Ui::AddSkip(container, st::settingsSendTypeSkip);
const auto skip = st::settingsSendTypeSkip;
auto wrap = object_ptr<Ui::VerticalLayout>(container);
const auto inner = wrap.data();
container->add(
object_ptr<Ui::OverrideMargins>(
container,
std::move(wrap),
QMargins(0, skip, 0, skip)));
SetupSupportSwitchSettings(controller, inner);
Ui::AddSkip(inner, st::settingsCheckboxesSkip);
inner->add(
object_ptr<Ui::Checkbox>(
inner,
"Enable templates autocomplete",
controller->session().settings().supportTemplatesAutocomplete(),
st::settingsCheckbox),
st::settingsSendTypePadding
)->checkedChanges(
) | rpl::on_next([=](bool checked) {
controller->session().settings().setSupportTemplatesAutocomplete(
checked);
controller->session().saveSettingsDelayed();
}, inner->lifetime());
inner->add(
object_ptr<Ui::Checkbox>(
inner,
"Send all messages without sound",
controller->session().settings().supportAllSilent(),
st::settingsCheckbox),
st::settingsSendTypePadding
)->checkedChanges(
) | rpl::on_next([=](bool checked) {
controller->session().settings().setSupportAllSilent(
checked);
controller->session().saveSettingsDelayed();
}, inner->lifetime());
Ui::AddSkip(inner, st::settingsCheckboxesSkip);
Ui::AddSubsectionTitle(inner, rpl::single(u"Load chats for a period"_q));
SetupSupportChatsLimitSlice(controller, inner);
Ui::AddSkip(inner, st::settingsCheckboxesSkip);
Ui::AddSkip(inner);
}
Chat::Chat(QWidget *parent, not_null<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent();
}
rpl::producer<QString> Chat::title() {
return tr::lng_settings_section_chat_settings();
}
void Chat::fillTopBarMenu(const Ui::Menu::MenuCallback &addAction) {
const auto window = &controller()->window();
const auto createTheme = addAction(
tr::lng_settings_bg_theme_create(tr::now),
[=] { window->show(Box(Window::Theme::CreateBox, window)); },
&st::menuIconChangeColors);
createTheme->setProperty(
"highlight-control-id",
u"chat/themes-create"_q);
}
void Chat::setupContent() {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
build(content, Builder::ChatSection);
Ui::ResizeFitChild(this, content);
}
namespace Builder {
SectionBuildMethod ChatSection = kMeta.build;
} // namespace Builder
} // namespace Settings