/* 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/settings_builder.h" #include "apiwrap.h" #include "lang/lang_keys.h" #include "api/api_user_privacy.h" #include "boxes/edit_privacy_box.h" #include "main/main_session.h" #include "settings/settings_common.h" #include "settings/sections/settings_privacy_security.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" #include "styles/style_settings.h" namespace Settings::Builder { namespace { [[nodiscard]] QString ResolveTitle(rpl::producer title) { auto result = QString(); auto lifetime = rpl::lifetime(); std::move(title).start( [&](QString value) { result = std::move(value); }, [](auto&&) {}, [] {}, lifetime); return result; } } // namespace BuildHelper::BuildHelper( SectionMeta &&meta, FnMut method) : build([=]( not_null container, not_null controller, Fn showOther, rpl::producer<> showFinished) { auto &lifetime = container->lifetime(); const auto highlights = lifetime.make_state(); const auto isPaused = Window::PausedIn( controller, Window::GifPauseReason::Layer); auto builder = SectionBuilder(WidgetContext{ .container = static_cast( container->add(object_ptr( container, object_ptr(container)))->entity()), .controller = controller, .showOther = std::move(showOther), .isPaused = isPaused, .highlights = highlights, }); _method(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); }) , _meta(std::move(meta)) , _method(std::move(method)) { Expects(_method != nullptr); SearchRegistry::Instance().add( &_meta, [=](not_null<::Main::Session*> session) { return index(session); }); } SearchRegistry &SearchRegistry::Instance() { static SearchRegistry instance; return instance; } void SearchRegistry::add( not_null meta, SearchEntriesIndexer indexer) { _sections[meta->id] = meta; _indexers.push_back({ meta->id, std::move(indexer) }); } std::vector SearchRegistry::collectAll( not_null session) const { auto result = std::vector(); for (const auto &[sectionId, meta] : _sections) { if (meta->parentId) { result.push_back({ .title = (*meta->title)(tr::now), .section = sectionId, .icon = { meta->icon }, }); } } for (const auto &entry : _indexers) { auto entries = entry.indexer(session); result.insert(result.end(), entries.begin(), entries.end()); } return result; } QString SearchRegistry::sectionTitle(Type sectionId) const { const auto it = _sections.find(sectionId); return (it != _sections.end()) ? (*it->second->title)(tr::now) : QString(); } QString SearchRegistry::sectionPath(Type sectionId) const { auto parts = QStringList(); const auto start = _sections.find(sectionId); auto current = (start != _sections.end()) ? start->second->parentId : nullptr; while (current) { if (const auto title = sectionTitle(current); !title.isEmpty()) { parts.prepend(title); } const auto it = _sections.find(current); current = (it != _sections.end()) ? it->second->parentId : nullptr; } return parts.join(u" > "_q); } std::vector BuildHelper::index( not_null session) const { auto entries = std::vector(); auto builder = SectionBuilder(SearchContext{ .sectionId = _meta.id, .session = session, .entries = &entries, }); _method(builder); for (auto &entry : entries) { if (!entry.section) { entry.section = _meta.id; } } return entries; } SectionBuilder::SectionBuilder(BuildContext context) : _context(std::move(context)) { } void SectionBuilder::add(FnMut method) { Expects(method != nullptr); method(_context); } Ui::VerticalLayout *SectionBuilder::scope( FnMut method, rpl::producer shown, FnMut hook) { auto result = (Ui::VerticalLayout*)nullptr; v::match(_context, [&](WidgetContext &wctx) { const auto outer = wctx.container; const auto toggled = (shown || hook); const auto wrap = toggled ? outer->add( object_ptr>( outer, object_ptr(outer))) : nullptr; const auto inner = wrap ? wrap->entity() : outer->add(object_ptr(outer)); wctx.container = inner; method(); if (shown) { wrap->toggleOn(std::move(shown)); wrap->finishAnimating(); } if (hook) { hook(wrap); } wctx.container = outer; result = inner; }, [&](const SearchContext &sctx) { method(); }); return result; } Ui::RpWidget *SectionBuilder::add( FnMut widget, FnMut search) { auto result = (Ui::RpWidget*)nullptr; add([&](const BuildContext &ctx) { v::match(ctx, [&](const WidgetContext &wctx) { if (auto w = widget ? widget(wctx) : WidgetToAdd()) { result = w.widget.data(); wctx.container->add(std::move(w.widget), w.margin, w.align); if (auto entry = search ? search() : SearchEntry()) { if (wctx.highlights) { wctx.highlights->push_back({ std::move(entry.id), { result, std::move(w.highlight) }, }); } } } }, [&](const SearchContext &sctx) { if (auto entry = search ? search() : SearchEntry()) { entry.section = sctx.sectionId; sctx.entries->push_back(std::move(entry)); } }); }); return result; } Ui::RpWidget *SectionBuilder::addControl(ControlArgs &&args) { if (auto shown = base::take(args.shown)) { auto result = (Ui::RpWidget*)nullptr; scope([&] { result = addControl(std::move(args)); }, std::move(shown)); return result; } return add([&](const WidgetContext &ctx) { return WidgetToAdd{ .widget = args.factory ? args.factory(ctx.container) : nullptr, .margin = args.margin, .align = args.align, .highlight = std::move(args.highlight), }; }, [&]() mutable { return SearchEntry{ .id = std::move(args.id), .title = ResolveTitle(std::move(args.title)), .keywords = std::move(args.keywords), .icon = std::move(args.searchIcon), .checkIcon = args.searchCheckIcon, }; }); } Ui::SettingsButton *SectionBuilder::addButton(ButtonArgs &&args) { const auto &st = args.st ? *args.st : st::settingsButton; auto iconForSearch = IconDescriptor{ args.icon.icon }; const auto factory = [&](not_null container) { auto button = CreateButtonWithIcon( args.container ? args.container : container.get(), rpl::duplicate(args.title), st, std::move(args.icon)); if (button && args.onClick) { button->addClickHandler(std::move(args.onClick)); } if (args.label) { CreateRightLabel( button.data(), std::move(args.label), st, rpl::duplicate(args.title)); } if (args.toggled) { button->toggleOn(std::move(args.toggled)); } return std::move(button); }; return static_cast(addControl({ .factory = factory, .id = std::move(args.id), .title = rpl::duplicate(args.title), .highlight = std::move(args.highlight), .shown = std::move(args.shown), .keywords = std::move(args.keywords), .searchIcon = std::move(iconForSearch), })); } Ui::SettingsButton *SectionBuilder::addSectionButton(SectionArgs &&args) { const auto wctx = std::get_if(&_context); const auto showOther = wctx ? wctx->showOther : nullptr; const auto target = args.targetSection; return addButton({ .id = std::move(args.id), .title = std::move(args.title), .icon = std::move(args.icon), .onClick = [=] { showOther(target); }, .keywords = std::move(args.keywords), }); } void SectionBuilder::addDivider() { v::match(_context, [&](const WidgetContext &ctx) { Ui::AddDivider(ctx.container); }, [](const SearchContext &) { }); } void SectionBuilder::addSkip() { v::match(_context, [&](const WidgetContext &ctx) { Ui::AddSkip(ctx.container); }, [](const SearchContext &) { }); } void SectionBuilder::addSkip(int height) { v::match(_context, [&](const WidgetContext &ctx) { Ui::AddSkip(ctx.container, height); }, [](const SearchContext &) { }); } void SectionBuilder::addDividerText(rpl::producer text) { v::match(_context, [&](const WidgetContext &ctx) { Ui::AddDividerText(ctx.container, std::move(text)); }, [](const SearchContext &) { }); } Ui::SettingsButton *SectionBuilder::addPremiumButton(PremiumButtonArgs &&args) { const auto result = addButton({ .id = std::move(args.id), .title = std::move(args.title), .label = std::move(args.label), .onClick = std::move(args.onClick), .keywords = std::move(args.keywords), }); if (result) { AddPremiumStar( result, args.credits, v::get(_context).isPaused); } return result; } Ui::SettingsButton *SectionBuilder::addPrivacyButton(PrivacyButtonArgs &&args) { const auto controller = this->controller(); const auto session = this->session(); const auto button = addButton({ .id = args.id, .title = rpl::duplicate(args.title), .st = &st::settingsButtonNoIcon, .label = PrivacyButtonLabel(session, args.key), .keywords = args.keywords, }); if (button) { const auto id = args.id; const auto premium = args.premium; const auto shower = Ui::CreateChild(button); const auto factory = args.controllerFactory; button->addClickHandler([=, key = args.key] { *shower = session->api().userPrivacy().value( key ) | rpl::take( 1 ) | rpl::on_next(crl::guard(controller, [=]( const Api::UserPrivacy::Rule &value) { controller->show(Box( controller, factory(), value)); })); }); if (premium) { AddPrivacyPremiumStar( button, session, std::move(args.title), st::settingsButtonNoIcon.padding); } } return button; } Ui::Checkbox *SectionBuilder::addCheckbox(CheckboxArgs &&args) { const auto factory = [&](not_null container) { return object_ptr( container, ResolveTitle(rpl::duplicate(args.title)), args.checked, st::settingsCheckbox); }; return static_cast(addControl({ .factory = factory, .id = std::move(args.id), .title = rpl::duplicate(args.title), .margin = st::settingsCheckboxPadding, .highlight = std::move(args.highlight), .shown = std::move(args.shown), .keywords = std::move(args.keywords), .searchCheckIcon = (args.checked ? SearchEntryCheckIcon::Checked : SearchEntryCheckIcon::Unchecked), })); } void SectionBuilder::addSubsectionTitle(SubsectionTitleArgs &&args) { v::match(_context, [&](const WidgetContext &ctx) { const auto title = AddSubsectionTitle( ctx.container, rpl::duplicate(args.title)); if (!args.id.isEmpty() && ctx.highlights) { ctx.highlights->push_back({ args.id, { title.get(), SubsectionTitleHighlight() }, }); } }, [&](const SearchContext &ctx) { if (!args.id.isEmpty()) { ctx.entries->push_back({ .id = std::move(args.id), .title = ResolveTitle(std::move(args.title)), .keywords = std::move(args.keywords), .section = ctx.sectionId, }); } }); } void SectionBuilder::addSubsectionTitle(rpl::producer text) { v::match(_context, [&](const WidgetContext &ctx) { AddSubsectionTitle(ctx.container, std::move(text)); }, [](const SearchContext &) { }); } Ui::VerticalLayout *SectionBuilder::container() const { return v::match(_context, [](const WidgetContext &ctx) { return ctx.container.get(); }, [](const SearchContext &) -> Ui::VerticalLayout* { return nullptr; }); } Window::SessionController *SectionBuilder::controller() const { return v::match(_context, [](const WidgetContext &ctx) { return ctx.controller.get(); }, [](const SearchContext &) -> Window::SessionController* { return nullptr; }); } not_null SectionBuilder::session() const { return v::match(_context, [](const WidgetContext &ctx) { return &ctx.controller->session(); }, [](const SearchContext &ctx) { return ctx.session.get(); }); } Fn SectionBuilder::showOther() const { return v::match(_context, [](const WidgetContext &ctx) { return ctx.showOther; }, [](const SearchContext &) -> Fn { return nullptr; }); } HighlightRegistry *SectionBuilder::highlights() const { return v::match(_context, [](const WidgetContext &ctx) { return ctx.highlights; }, [](const SearchContext &) -> HighlightRegistry* { return nullptr; }); } void SectionBuilder::registerHighlight( QString id, QWidget *widget, HighlightArgs &&args) { v::match(_context, [&](const WidgetContext &ctx) { if (ctx.highlights && widget) { ctx.highlights->push_back({ std::move(id), { widget, std::move(args) }, }); } }, [](const SearchContext &) { }); } } // namespace Settings::Builder