Upgrade settings_folders to full builder pattern

- Move Folders class to anonymous namespace in .cpp
- Export only Type FoldersId() from header
- setupContent() uses custom SectionBuildMethod capturing state
- Widget creation through builder.add() callbacks
- Highlight registration via push_back

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Preston
2026-01-14 18:11:28 +04:00
parent 4c5cf47bc9
commit df347ad0f4
8 changed files with 492 additions and 374 deletions
@@ -478,7 +478,7 @@ void RegisterSettingsHandlers(Router &router) {
router.add(u"settings"_q, {
.path = u"folders"_q,
.action = SettingsSection{ ::Settings::Folders::Id() },
.action = SettingsSection{ ::Settings::FoldersId() },
});
router.add(u"settings"_q, {
@@ -1801,28 +1801,28 @@ void RegisterSettingsHandlers(Router &router) {
router.add(u"settings"_q, {
.path = u"folders/create"_q,
.action = SettingsControl{
::Settings::Folders::Id(),
::Settings::FoldersId(),
u"folders/create"_q,
},
});
router.add(u"settings"_q, {
.path = u"folders/add-recommended"_q,
.action = SettingsControl{
::Settings::Folders::Id(),
::Settings::FoldersId(),
u"folders/add-recommended"_q,
},
});
router.add(u"settings"_q, {
.path = u"folders/show-tags"_q,
.action = SettingsControl{
::Settings::Folders::Id(),
::Settings::FoldersId(),
u"folders/show-tags"_q,
},
});
router.add(u"settings"_q, {
.path = u"folders/tab-view"_q,
.action = SettingsControl{
::Settings::Folders::Id(),
::Settings::FoldersId(),
u"folders/tab-view"_q,
},
});
@@ -825,7 +825,7 @@ bool ResolveSettings(
} else if (section == u"devices"_q) {
return ::Settings::Sessions::Id();
} else if (section == u"folders"_q) {
return ::Settings::Folders::Id();
return ::Settings::FoldersId();
} else if (section == u"privacy"_q) {
return ::Settings::PrivacySecurity::Id();
} else if (section == u"themes"_q) {
@@ -25,8 +25,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lottie/lottie_icon.h"
#include "main/main_session.h"
#include "settings/sections/settings_main.h"
#include "settings/settings_builder.h"
#include "settings/sections/settings_premium.h"
#include "settings/settings_builder.h"
#include "settings/settings_common_session.h"
#include "ui/boxes/confirm_box.h"
#include "ui/empty_userpic.h"
#include "ui/filter_icons.h"
@@ -44,21 +45,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/slide_wrap.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
#include "styles/style_settings.h"
#include "styles/style_layers.h"
#include "styles/style_boxes.h"
#include "styles/style_chat_helpers.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace Settings {
struct FoldersHighlightTargets {
QPointer<Ui::RpWidget> createButton;
QPointer<Ui::RpWidget> tagsButton;
QPointer<Ui::RpWidget> viewSection;
QPointer<Ui::RpWidget> recommendedTitle;
};
namespace {
using namespace Builder;
@@ -358,30 +351,26 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
}
}
[[nodiscard]] Fn<void()> SetupFoldersContent(
struct FoldersState {
std::vector<FilterRow> rows;
rpl::variable<int> count;
rpl::variable<int> suggested;
Fn<void(const FilterRowButton*, Fn<void(Data::ChatFilter)>)> save;
Ui::Animations::Simple tagsEnabledAnimation;
rpl::event_stream<bool> tagsButtonEnabled;
};
void SetupFoldersList(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container,
not_null<rpl::event_stream<bool>*> tagsButtonEnabled,
FoldersHighlightTargets *targets) {
auto &lifetime = container->lifetime();
not_null<FoldersState*> state,
HighlightRegistry *highlights) {
const auto weak = base::make_weak(container);
const auto session = &controller->session();
const auto limit = [=] {
return Data::PremiumLimits(session).dialogFiltersCurrent();
};
Ui::AddSkip(container, st::defaultVerticalListSkip);
Ui::AddSubsectionTitle(container, tr::lng_filters_subtitle());
struct State {
std::vector<FilterRow> rows;
rpl::variable<int> count;
rpl::variable<int> suggested;
Fn<void(const FilterRowButton*, Fn<void(Data::ChatFilter)>)> save;
Ui::Animations::Simple tagsEnabledAnimation;
};
const auto state = lifetime.make_state<State>();
const auto find = [=](not_null<FilterRowButton*> button) {
const auto i = ranges::find(state->rows, button, &FilterRow::button);
Assert(i != end(state->rows));
@@ -574,8 +563,8 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
tr::lng_filters_create(),
st::settingsButtonActive,
{ &st::settingsIconAdd, IconType::Round, &st::windowBgActive });
if (targets) {
targets->createButton = createButton;
if (highlights) {
highlights->push_back({ u"folders/create"_q, { createButton.get() } });
}
createButton->setClickedCallback([=] {
if (showLimitReached()) {
@@ -603,81 +592,6 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
crl::guard(container, doneCallback),
crl::guard(container, saveAnd)));
});
Ui::AddSkip(container);
const auto nonEmptyAbout = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container))
)->setDuration(0);
const auto aboutRows = nonEmptyAbout->entity();
Ui::AddDivider(aboutRows);
Ui::AddSkip(aboutRows);
const auto recommendedTitle = Ui::AddSubsectionTitle(
aboutRows,
tr::lng_filters_recommended());
if (targets) {
targets->recommendedTitle = recommendedTitle;
}
const auto setTagsProgress = [=](float64 value) {
for (const auto &row : state->rows) {
row.button->setColorIndexProgress(value);
}
};
tagsButtonEnabled->events() | rpl::distinct_until_changed(
) | rpl::on_next([=](bool value) {
state->tagsEnabledAnimation.stop();
state->tagsEnabledAnimation.start(
setTagsProgress,
value ? .0 : 1.,
value ? 1. : .0,
st::universalDuration);
}, lifetime);
setTagsProgress(session->data().chatsFilters().tagsEnabled());
rpl::single(rpl::empty) | rpl::then(
session->data().chatsFilters().suggestedUpdated()
) | rpl::map([=] {
return session->data().chatsFilters().suggestedFilters();
}) | rpl::filter([=](const std::vector<Data::SuggestedFilter> &list) {
return !list.empty();
}) | rpl::take(
1
) | rpl::on_next([=](
const std::vector<Data::SuggestedFilter> &suggestions) {
for (const auto &suggestion : suggestions) {
const auto &filter = suggestion.filter;
if (ranges::contains(state->rows, filter, &FilterRow::filter)) {
continue;
}
state->suggested = state->suggested.current() + 1;
const auto button = aboutRows->add(object_ptr<FilterRowButton>(
aboutRows,
session,
filter,
suggestion.description));
button->addRequests(
) | rpl::on_next([=] {
if (showLimitReached()) {
return;
}
addFilter(filter);
state->suggested = state->suggested.current() - 1;
delete button;
}, button->lifetime());
}
aboutRows->resizeToWidth(container->width());
Ui::AddSkip(aboutRows, st::defaultVerticalListSkip);
}, aboutRows->lifetime());
auto showSuggestions = rpl::combine(
state->suggested.value(),
state->count.value(),
Data::AmPremiumValue(session)
) | rpl::map([limit](int suggested, int count, bool) {
return suggested > 0 && count < limit();
});
nonEmptyAbout->toggleOn(std::move(showSuggestions));
const auto prepareGoodIdsForNewFilters = [=] {
const auto &list = session->data().chatsFilters().list();
@@ -851,190 +765,483 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
checkFinished();
});
};
return [copy = state->save] {
copy(nullptr, nullptr);
};
}
void SetupTopContent(
not_null<Ui::VerticalLayout*> parent,
rpl::producer<> showFinished) {
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(parent.get());
const auto verticalLayout = parent->add(
object_ptr<Ui::VerticalLayout>(parent.get()));
auto icon = CreateLottieIcon(
verticalLayout,
{
.name = u"filters"_q,
.sizeOverride = {
st::settingsFilterIconSize,
st::settingsFilterIconSize,
},
},
st::settingsFilterIconPadding);
std::move(
showFinished
) | rpl::on_next([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
}, verticalLayout->lifetime());
verticalLayout->add(std::move(icon.widget));
verticalLayout->add(
object_ptr<Ui::FlatLabel>(
verticalLayout,
tr::lng_filters_about(),
st::settingsFilterDividerLabel),
st::settingsFilterDividerLabelPadding,
style::al_top)->setTryMakeSimilarLines(true);
verticalLayout->geometryValue(
) | rpl::on_next([=](const QRect &r) {
divider->setGeometry(r);
}, divider->lifetime());
}
void SetupTagContent(
void SetupRecommendedSection(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> content,
not_null<rpl::event_stream<bool>*> tagsButtonEnabled,
FoldersHighlightTargets *targets) {
Ui::AddDivider(content);
Ui::AddSkip(content);
not_null<Ui::VerticalLayout*> container,
not_null<FoldersState*> state,
HighlightRegistry *highlights) {
const auto session = &controller->session();
struct State final {
rpl::event_stream<bool> tagsTurnOff;
base::Timer requestTimer;
Fn<void()> sendCallback;
const auto limit = [=] {
return Data::PremiumLimits(session).dialogFiltersCurrent();
};
auto premium = Data::AmPremiumValue(session);
const auto tagsButton = content->add(
object_ptr<Ui::SettingsButton>(
content,
tr::lng_filters_enable_tags(),
st::settingsButtonNoIconLocked));
if (targets) {
targets->tagsButton = tagsButton;
}
const auto state = tagsButton->lifetime().make_state<State>();
tagsButton->toggleOn(rpl::merge(
rpl::combine(
session->data().chatsFilters().tagsEnabledValue(),
rpl::duplicate(premium),
rpl::mappers::_1 && rpl::mappers::_2),
state->tagsTurnOff.events()));
rpl::duplicate(premium) | rpl::on_next([=](bool value) {
tagsButton->setToggleLocked(!value);
}, tagsButton->lifetime());
const auto showLimitReached = [=] {
const auto removed = ranges::count_if(
state->rows,
&FilterRow::removed);
const auto count = int(state->rows.size() - removed);
if (count < limit()) {
return false;
}
controller->show(Box(FiltersLimitBox, session, count));
return true;
};
const auto send = [=, weak = base::make_weak(tagsButton)](bool checked) {
session->data().chatsFilters().requestToggleTags(checked, [=] {
if ([[maybe_unused]] const auto strong = weak.get()) {
state->tagsTurnOff.fire(!checked);
const auto find = [=](not_null<FilterRowButton*> button) {
const auto i = ranges::find(state->rows, button, &FilterRow::button);
Assert(i != end(state->rows));
return &*i;
};
const auto addFilter = [=, wrap = container->parentWidget()](
const Data::ChatFilter &filter) {
const auto outerWrap = static_cast<Ui::VerticalLayout*>(wrap);
const auto button = outerWrap->insert(
outerWrap->count() - 1,
object_ptr<FilterRowButton>(outerWrap, session, filter));
button->removeRequests(
) | rpl::on_next([=] {
const auto row = find(button);
row->removed = true;
button->setRemoved(true);
}, button->lifetime());
button->restoreRequests(
) | rpl::on_next([=] {
if (showLimitReached()) {
return;
}
button->setRemoved(false);
find(button)->removed = false;
}, button->lifetime());
button->setClickedCallback([=] {
const auto found = find(button);
if (found->removed) {
return;
}
const auto doneCallback = [=](const Data::ChatFilter &result) {
find(button)->filter = result;
button->updateData(result);
};
const auto saveAnd = [=](
const Data::ChatFilter &data,
Fn<void(Data::ChatFilter)> next) {
doneCallback(data);
state->save(button, next);
};
controller->window().show(Box(
EditFilterBox,
controller,
found->filter,
crl::guard(button, doneCallback),
crl::guard(button, saveAnd)));
});
state->rows.push_back({ button, filter });
state->count = state->rows.size();
return button;
};
Ui::AddSkip(container);
const auto nonEmptyAbout = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container))
)->setDuration(0);
const auto aboutRows = nonEmptyAbout->entity();
Ui::AddDivider(aboutRows);
Ui::AddSkip(aboutRows);
const auto recommendedTitle = Ui::AddSubsectionTitle(
aboutRows,
tr::lng_filters_recommended());
if (highlights) {
highlights->push_back({
u"folders/add-recommended"_q,
{ recommendedTitle.get(), SubsectionTitleHighlight() },
});
}
const auto setTagsProgress = [=](float64 value) {
for (const auto &row : state->rows) {
row.button->setColorIndexProgress(value);
}
};
state->tagsButtonEnabled.events() | rpl::distinct_until_changed(
) | rpl::on_next([=](bool value) {
state->tagsEnabledAnimation.stop();
state->tagsEnabledAnimation.start(
setTagsProgress,
value ? .0 : 1.,
value ? 1. : .0,
st::universalDuration);
}, container->lifetime());
setTagsProgress(session->data().chatsFilters().tagsEnabled());
rpl::single(rpl::empty) | rpl::then(
session->data().chatsFilters().suggestedUpdated()
) | rpl::map([=] {
return session->data().chatsFilters().suggestedFilters();
}) | rpl::filter([=](const std::vector<Data::SuggestedFilter> &list) {
return !list.empty();
}) | rpl::take(
1
) | rpl::on_next([=](
const std::vector<Data::SuggestedFilter> &suggestions) {
for (const auto &suggestion : suggestions) {
const auto &filter = suggestion.filter;
if (ranges::contains(state->rows, filter, &FilterRow::filter)) {
continue;
}
state->suggested = state->suggested.current() + 1;
const auto button = aboutRows->add(object_ptr<FilterRowButton>(
aboutRows,
session,
filter,
suggestion.description));
button->addRequests(
) | rpl::on_next([=] {
if (showLimitReached()) {
return;
}
addFilter(filter);
state->suggested = state->suggested.current() - 1;
delete button;
}, button->lifetime());
}
aboutRows->resizeToWidth(container->width());
Ui::AddSkip(aboutRows, st::defaultVerticalListSkip);
}, aboutRows->lifetime());
auto showSuggestions = rpl::combine(
state->suggested.value(),
state->count.value(),
Data::AmPremiumValue(session)
) | rpl::map([limit](int suggested, int count, bool) {
return suggested > 0 && count < limit();
});
nonEmptyAbout->toggleOn(std::move(showSuggestions));
}
void BuildTopContent(SectionBuilder &builder, rpl::producer<> showFinished) {
builder.add([showFinished = std::move(showFinished)](
const WidgetContext &ctx) mutable {
const auto parent = ctx.container;
const auto divider = Ui::CreateChild<Ui::BoxContentDivider>(
parent.get());
const auto verticalLayout = parent->add(
object_ptr<Ui::VerticalLayout>(parent.get()));
auto icon = CreateLottieIcon(
verticalLayout,
{
.name = u"filters"_q,
.sizeOverride = {
st::settingsFilterIconSize,
st::settingsFilterIconSize,
},
},
st::settingsFilterIconPadding);
std::move(
showFinished
) | rpl::on_next([animate = std::move(icon.animate)] {
animate(anim::repeat::once);
}, verticalLayout->lifetime());
verticalLayout->add(std::move(icon.widget));
verticalLayout->add(
object_ptr<Ui::FlatLabel>(
verticalLayout,
tr::lng_filters_about(),
st::settingsFilterDividerLabel),
st::settingsFilterDividerLabelPadding,
style::al_top)->setTryMakeSimilarLines(true);
verticalLayout->geometryValue(
) | rpl::on_next([=](const QRect &r) {
divider->setGeometry(r);
}, divider->lifetime());
return SectionBuilder::WidgetToAdd{};
});
}
void BuildFoldersListSection(
SectionBuilder &builder,
not_null<FoldersState*> state) {
builder.addSkip();
builder.addSubsectionTitle(tr::lng_filters_subtitle());
builder.add([=](const WidgetContext &ctx) {
SetupFoldersList(ctx.controller, ctx.container, state, ctx.highlights);
SetupRecommendedSection(
ctx.controller,
ctx.container,
state,
ctx.highlights);
return SectionBuilder::WidgetToAdd{};
});
}
void BuildTagsSection(SectionBuilder &builder, not_null<FoldersState*> state) {
if (!builder.session()->premiumPossible()) {
return;
}
builder.addDivider();
builder.addSkip();
const auto session = builder.session();
builder.add([=](const WidgetContext &ctx) {
const auto controller = ctx.controller;
const auto content = ctx.container;
struct TagsState final {
rpl::event_stream<bool> tagsTurnOff;
base::Timer requestTimer;
Fn<void()> sendCallback;
};
auto premium = Data::AmPremiumValue(session);
const auto tagsButton = content->add(
object_ptr<Ui::SettingsButton>(
content,
tr::lng_filters_enable_tags(),
st::settingsButtonNoIconLocked));
if (ctx.highlights) {
ctx.highlights->push_back({ u"folders/show-tags"_q, { tagsButton } });
}
const auto tagsState = tagsButton->lifetime().make_state<TagsState>();
tagsButton->toggleOn(rpl::merge(
rpl::combine(
session->data().chatsFilters().tagsEnabledValue(),
rpl::duplicate(premium),
rpl::mappers::_1 && rpl::mappers::_2),
tagsState->tagsTurnOff.events()));
rpl::duplicate(premium) | rpl::on_next([=](bool value) {
tagsButton->setToggleLocked(!value);
}, tagsButton->lifetime());
const auto send = [=,
weak = base::make_weak(tagsButton)](bool checked) {
session->data().chatsFilters().requestToggleTags(checked, [=] {
if ([[maybe_unused]] const auto strong = weak.get()) {
tagsState->tagsTurnOff.fire(!checked);
}
});
};
tagsButton->toggledValue(
) | rpl::filter([=](bool checked) {
const auto premium = session->premium();
if (checked && !premium) {
ShowPremiumPreviewToBuy(controller, PremiumFeature::FilterTags);
tagsState->tagsTurnOff.fire(false);
}
if (!premium) {
state->tagsButtonEnabled.fire(false);
} else {
state->tagsButtonEnabled.fire_copy(checked);
}
const auto proceed = premium
&& (checked != session->data().chatsFilters().tagsEnabled());
if (!proceed) {
tagsState->requestTimer.cancel();
}
return proceed;
}) | rpl::on_next([=](bool v) {
tagsState->sendCallback = [=] { send(v); };
tagsState->requestTimer.cancel();
tagsState->requestTimer.setCallback([=] { send(v); });
tagsState->requestTimer.callOnce(500);
}, tagsButton->lifetime());
tagsButton->lifetime().add([=] {
if (tagsState->requestTimer.isActive()) {
if (tagsState->sendCallback) {
tagsState->sendCallback();
}
}
});
};
tagsButton->toggledValue(
) | rpl::filter([=](bool checked) {
const auto premium = session->premium();
if (checked && !premium) {
ShowPremiumPreviewToBuy(controller, PremiumFeature::FilterTags);
state->tagsTurnOff.fire(false);
}
if (!premium) {
tagsButtonEnabled->fire(false);
} else {
tagsButtonEnabled->fire_copy(checked);
}
const auto proceed = premium
&& (checked != session->data().chatsFilters().tagsEnabled());
if (!proceed) {
state->requestTimer.cancel();
}
return proceed;
}) | rpl::on_next([=](bool v) {
state->sendCallback = [=] { send(v); };
state->requestTimer.cancel();
state->requestTimer.setCallback([=] { send(v); });
state->requestTimer.callOnce(500);
}, tagsButton->lifetime());
tagsButton->lifetime().add([=] {
if (state->requestTimer.isActive()) {
if (state->sendCallback) {
state->sendCallback();
}
}
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"folders/show-tags"_q,
.title = tr::lng_filters_enable_tags(tr::now),
.keywords = { u"tags"_q, u"colors"_q, u"premium"_q },
};
});
Ui::AddSkip(content);
const auto about = Ui::AddDividerText(
content,
rpl::conditional(
rpl::duplicate(premium),
tr::lng_filters_enable_tags_about(tr::rich),
tr::lng_filters_enable_tags_about_premium(
lt_link,
tr::lng_effect_premium_link() | rpl::map([](QString t) {
return tr::link(std::move(t), u"internal:"_q);
}),
tr::rich)));
about->setClickHandlerFilter([=](const auto &...) {
Settings::ShowPremium(controller, u"folder_tags"_q);
return true;
builder.addSkip();
builder.add([=](const WidgetContext &ctx) {
auto premium = Data::AmPremiumValue(session);
const auto about = Ui::AddDividerText(
ctx.container,
rpl::conditional(
rpl::duplicate(premium),
tr::lng_filters_enable_tags_about(tr::rich),
tr::lng_filters_enable_tags_about_premium(
lt_link,
tr::lng_effect_premium_link() | rpl::map([](QString t) {
return tr::link(std::move(t), u"internal:"_q);
}),
tr::rich)));
about->setClickHandlerFilter([=](const auto &...) {
Settings::ShowPremium(ctx.controller, u"folder_tags"_q);
return true;
});
return SectionBuilder::WidgetToAdd{};
});
}
void SetupView(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> content,
bool dividerNeeded,
FoldersHighlightTargets *targets) {
const auto wrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)));
wrap->toggleOn(controller->enoughSpaceForFiltersValue());
if (targets) {
targets->viewSection = wrap->entity();
}
content = wrap->entity();
void BuildViewSection(SectionBuilder &builder) {
builder.add([](const WidgetContext &ctx) {
const auto controller = ctx.controller;
const auto parent = ctx.container;
const auto wrap = parent->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
parent,
object_ptr<Ui::VerticalLayout>(parent)));
wrap->toggleOn(controller->enoughSpaceForFiltersValue());
if (ctx.highlights) {
ctx.highlights->push_back({ u"folders/tab-view"_q, { wrap->entity() } });
}
const auto content = wrap->entity();
if (dividerNeeded) {
Ui::AddDivider(content);
}
Ui::AddSkip(content);
Ui::AddSubsectionTitle(content, tr::lng_filters_view_subtitle());
Ui::AddSkip(content);
Ui::AddSubsectionTitle(content, tr::lng_filters_view_subtitle());
const auto group = std::make_shared<Ui::RadioenumGroup<bool>>(
Core::App().settings().chatFiltersHorizontal());
const auto addSend = [&](bool value, const QString &text) {
content->add(
object_ptr<Ui::Radioenum<bool>>(
content,
group,
value,
text,
st::settingsSendType),
st::settingsSendTypePadding);
};
addSend(false, tr::lng_filters_vertical(tr::now));
addSend(true, tr::lng_filters_horizontal(tr::now));
const auto group = std::make_shared<Ui::RadioenumGroup<bool>>(
Core::App().settings().chatFiltersHorizontal());
const auto addSend = [&](bool value, const QString &text) {
content->add(
object_ptr<Ui::Radioenum<bool>>(
content,
group,
value,
text,
st::settingsSendType),
st::settingsSendTypePadding);
};
addSend(false, tr::lng_filters_vertical(tr::now));
addSend(true, tr::lng_filters_horizontal(tr::now));
group->setChangedCallback([=](bool value) {
Core::App().settings().setChatFiltersHorizontal(value);
Core::App().saveSettingsDelayed();
group->setChangedCallback([=](bool value) {
Core::App().settings().setChatFiltersHorizontal(value);
Core::App().saveSettingsDelayed();
});
Ui::AddSkip(content);
Ui::AddSkip(content);
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"folders/tab-view"_q,
.title = tr::lng_filters_view_subtitle(tr::now),
.keywords = { u"view"_q, u"layout"_q, u"tabs"_q },
};
});
Ui::AddSkip(content);
Ui::AddSkip(content);
}
void BuildFoldersSectionContent(SectionBuilder &builder) {
class Folders : public Section<Folders> {
public:
Folders(
QWidget *parent,
not_null<Window::SessionController*> controller);
~Folders();
void showFinished() override;
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent();
std::shared_ptr<FoldersState> _state;
rpl::event_stream<> _showFinished;
};
Folders::Folders(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent, controller)
, _state(std::make_shared<FoldersState>()) {
setupContent();
}
Folders::~Folders() {
if (!Core::Quitting() && _state->save) {
_state->save(nullptr, nullptr);
}
}
rpl::producer<QString> Folders::title() {
return tr::lng_filters_title();
}
void Folders::setupContent() {
controller()->session().data().chatsFilters().requestSuggested();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto state = _state;
const SectionBuildMethod buildMethod = [state](
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void(Type)> showOther,
rpl::producer<> showFinished) {
auto &lifetime = container->lifetime();
const auto highlights = lifetime.make_state<HighlightRegistry>();
const auto isPaused = Window::PausedIn(
controller,
Window::GifPauseReason::Layer);
auto showFinishedDup = rpl::duplicate(showFinished);
auto builder = SectionBuilder(WidgetContext{
.container = container,
.controller = controller,
.showOther = std::move(showOther),
.isPaused = isPaused,
.highlights = highlights,
});
BuildTopContent(builder, std::move(showFinishedDup));
BuildFoldersListSection(builder, state.get());
BuildTagsSection(builder, state.get());
BuildViewSection(builder);
std::move(showFinished) | rpl::on_next([=] {
for (const auto &[id, entry] : *highlights) {
if (entry.widget) {
controller->checkHighlightControl(
id,
entry.widget,
base::duplicate(entry.args));
}
}
}, lifetime);
};
build(content, buildMethod);
Ui::ResizeFitChild(this, content);
}
void Folders::showFinished() {
_showFinished.fire({});
Section<Folders>::showFinished();
}
const auto kMeta = BuildHelper({
.id = Folders::Id(),
.parentId = Main::Id(),
.title = &tr::lng_filters_title,
.icon = &st::menuIconShowInFolder,
}, [](SectionBuilder &builder) {
builder.add(nullptr, [] {
return SearchEntry{
.id = u"folders/create"_q,
@@ -1068,77 +1275,12 @@ void BuildFoldersSectionContent(SectionBuilder &builder) {
.keywords = { u"view"_q, u"layout"_q, u"tabs"_q },
};
});
}
const auto kMeta = BuildHelper({
.id = Folders::Id(),
.parentId = Main::Id(),
.title = &tr::lng_filters_title,
.icon = &st::menuIconShowInFolder,
}, [](SectionBuilder &builder) {
BuildFoldersSectionContent(builder);
});
} // namespace
Folders::Folders(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent();
}
Folders::~Folders() {
if (!Core::Quitting()) {
_save();
}
}
rpl::producer<QString> Folders::title() {
return tr::lng_filters_title();
}
void Folders::setupContent() {
controller()->session().data().chatsFilters().requestSuggested();
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
const auto tagsButtonEnabled
= content->lifetime().make_state<rpl::event_stream<bool>>();
SetupTopContent(content, _showFinished.events());
auto targets = FoldersHighlightTargets();
_save = SetupFoldersContent(
controller(),
content,
tagsButtonEnabled,
&targets);
_createButton = targets.createButton;
_recommendedTitle = targets.recommendedTitle;
auto dividerNeeded = true;
if (controller()->session().premiumPossible()) {
SetupTagContent(controller(), content, tagsButtonEnabled, &targets);
_tagsButton = targets.tagsButton;
dividerNeeded = false;
}
SetupView(controller(), content, dividerNeeded, &targets);
_viewSection = targets.viewSection;
Ui::ResizeFitChild(this, content);
}
void Folders::showFinished() {
_showFinished.fire({});
controller()->checkHighlightControl(u"folders/create"_q, _createButton);
controller()->checkHighlightControl(u"folders/show-tags"_q, _tagsButton);
controller()->checkHighlightControl(u"folders/tab-view"_q, _viewSection);
controller()->checkHighlightControl(
u"folders/add-recommended"_q,
_recommendedTitle,
SubsectionTitleHighlight());
Type FoldersId() {
return Folders::Id();
}
namespace Builder {
@@ -7,38 +7,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "settings/settings_common_session.h"
#include "settings/settings_type.h"
namespace Window {
class SessionController;
} // namespace Window
namespace Settings {
class Folders : public Section<Folders> {
public:
Folders(
QWidget *parent,
not_null<Window::SessionController*> controller);
~Folders();
[[nodiscard]] Type FoldersId();
void showFinished() override;
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent();
Fn<void()> _save;
rpl::event_stream<> _showFinished;
QPointer<Ui::RpWidget> _createButton;
QPointer<Ui::RpWidget> _tagsButton;
QPointer<Ui::RpWidget> _viewSection;
QPointer<Ui::RpWidget> _recommendedTitle;
};
namespace Builder {
extern SectionBuildMethod FoldersSection;
} // namespace Builder
} // namespace Settings
@@ -391,7 +391,7 @@ void BuildSectionButtons(SectionBuilder &builder) {
.id = u"main/folders"_q,
.title = tr::lng_settings_section_filters(),
.icon = { &st::menuIconShowInFolder },
.onClick = [=] { showOther(Folders::Id()); },
.onClick = [=] { showOther(FoldersId()); },
.keywords = { u"filters"_q, u"tabs"_q },
.shown = std::move(shownProducer),
});
@@ -161,7 +161,7 @@ auto GenerateCodes() {
});
codes.emplace(u"folders"_q, [](SessionController *window) {
if (window) {
window->showSettings(Settings::Folders::Id());
window->showSettings(Settings::FoldersId());
}
});
codes.emplace(u"registertg"_q, [](SessionController *window) {
@@ -111,13 +111,13 @@ void ShowMenu(
auto openFiltersSettings = [=] {
const auto filters = &session->data().chatsFilters();
if (filters->suggestedLoaded()) {
controller->showSettings(Settings::Folders::Id());
controller->showSettings(Settings::FoldersId());
} else if (!state->waitingSuggested) {
state->waitingSuggested = true;
filters->requestSuggested();
filters->suggestedUpdated(
) | rpl::take(1) | rpl::on_next([=] {
controller->showSettings(Settings::Folders::Id());
controller->showSettings(Settings::FoldersId());
}, parent->lifetime());
}
};
@@ -360,13 +360,13 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
void FiltersMenu::openFiltersSettings() {
const auto filters = &_session->session().data().chatsFilters();
if (filters->suggestedLoaded()) {
_session->showSettings(Settings::Folders::Id());
_session->showSettings(Settings::FoldersId());
} else if (!_waitingSuggested) {
_waitingSuggested = true;
filters->requestSuggested();
filters->suggestedUpdated(
) | rpl::take(1) | rpl::on_next([=] {
_session->showSettings(Settings::Folders::Id());
_session->showSettings(Settings::FoldersId());
}, _outer.lifetime());
}
}