Rewrite settings_business to new builder pattern

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Preston
2026-01-14 16:27:27 +04:00
parent 8812b892c4
commit 4fc45c590e
6 changed files with 350 additions and 25 deletions
+2 -2
View File
@@ -1552,8 +1552,8 @@ PRIVATE
settings/sections/settings_chat.h
settings/sections/settings_blocked_peers.cpp
settings/sections/settings_blocked_peers.h
settings/settings_business.cpp
settings/settings_business.h
settings/sections/settings_business.cpp
settings/sections/settings_business.h
settings/sections/settings_calls.cpp
settings/sections/settings_calls.h
settings/settings_codes.cpp
@@ -34,7 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "ui/painter.h"
#include "ui/vertical_list.h"
#include "settings/settings_business.h"
#include "settings/sections/settings_business.h"
#include "settings/settings_premium.h"
#include "lottie/lottie_single_player.h"
#include "history/view/media/history_view_sticker.h"
@@ -41,7 +41,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/sections/settings_active_sessions.h"
#include "settings/sections/settings_advanced.h"
#include "settings/sections/settings_blocked_peers.h"
#include "settings/settings_business.h"
#include "settings/sections/settings_business.h"
#include "settings/sections/settings_calls.h"
#include "settings/sections/settings_chat.h"
#include "settings/settings_passkeys.h"
@@ -5,24 +5,25 @@ 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_business.h"
#include "base/call_delayed.h"
#include "settings/sections/settings_business.h"
#include "api/api_chat_links.h"
#include "api/api_premium.h"
#include "apiwrap.h"
#include "base/call_delayed.h"
#include "boxes/premium_preview_box.h"
#include "core/click_handler_types.h"
#include "core/ui_integration.h" // TextContext
#include "data/business/data_business_info.h"
#include "core/ui_integration.h"
#include "data/business/data_business_chatbots.h"
#include "data/business/data_business_info.h"
#include "data/business/data_shortcut_messages.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_changes.h"
#include "data/data_peer_values.h" // AmPremiumValue.
#include "data/data_peer_values.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "info/info_wrap_widget.h" // Info::Wrap.
#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData.
#include "data/stickers/data_custom_emoji.h"
#include "info/info_wrap_widget.h"
#include "info/settings/info_settings_widget.h"
#include "lang/lang_keys.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
@@ -34,36 +35,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/business/settings_location.h"
#include "settings/business/settings_quick_replies.h"
#include "settings/business/settings_working_hours.h"
#include "settings/sections/settings_main.h"
#include "settings/settings_builder.h"
#include "settings/settings_common.h"
#include "settings/settings_common_session.h"
#include "settings/settings_premium.h"
#include "ui/controls/swipe_handler.h"
#include "ui/controls/swipe_handler_data.h"
#include "ui/effects/gradient.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
#include "ui/new_badges.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup.
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "ui/new_badges.h"
#include "ui/vertical_list.h"
#include "ui/controls/swipe_handler.h"
#include "ui/controls/swipe_handler_data.h"
#include "window/window_session_controller.h"
#include "apiwrap.h"
#include "api/api_premium.h"
#include "styles/style_premium.h"
#include "styles/style_channel_earn.h"
#include "styles/style_chat.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include "styles/style_chat.h"
#include "styles/style_channel_earn.h"
namespace Settings {
namespace {
using namespace Builder;
struct Entry {
const style::icon *icon;
rpl::producer<QString> title;
@@ -293,7 +297,6 @@ void AddBusinessSummary(
content->resizeToWidth(content->height());
// Icons.
Assert(iconContainers.size() > 2);
const auto from = iconContainers.front()->y();
const auto to = iconContainers.back()->y() + iconSize.height();
@@ -328,6 +331,308 @@ void AddBusinessSummary(
Ui::AddSkip(content, descriptionPadding.bottom());
}
[[nodiscard]] QString FeatureSearchId(PremiumFeature feature) {
switch (feature) {
case PremiumFeature::GreetingMessage: return u"business/greeting"_q;
case PremiumFeature::AwayMessage: return u"business/away"_q;
case PremiumFeature::QuickReplies: return u"business/replies"_q;
case PremiumFeature::BusinessHours: return u"business/hours"_q;
case PremiumFeature::BusinessLocation: return u"business/location"_q;
case PremiumFeature::ChatLinks: return u"business/links"_q;
case PremiumFeature::ChatIntro: return u"business/intro"_q;
case PremiumFeature::BusinessBots: return u"business/bots"_q;
case PremiumFeature::FilterTags: return u"business/tags"_q;
default: return QString();
}
}
[[nodiscard]] QString FeatureSearchTitle(PremiumFeature feature) {
switch (feature) {
case PremiumFeature::GreetingMessage:
return tr::lng_business_subtitle_greeting_messages(tr::now);
case PremiumFeature::AwayMessage:
return tr::lng_business_subtitle_away_messages(tr::now);
case PremiumFeature::QuickReplies:
return tr::lng_business_subtitle_quick_replies(tr::now);
case PremiumFeature::BusinessHours:
return tr::lng_business_subtitle_opening_hours(tr::now);
case PremiumFeature::BusinessLocation:
return tr::lng_business_subtitle_location(tr::now);
case PremiumFeature::ChatLinks:
return tr::lng_business_subtitle_chat_links(tr::now);
case PremiumFeature::ChatIntro:
return tr::lng_business_subtitle_chat_intro(tr::now);
case PremiumFeature::BusinessBots:
return tr::lng_business_subtitle_chatbots(tr::now);
case PremiumFeature::FilterTags:
return tr::lng_premium_summary_subtitle_filter_tags(tr::now);
default: return QString();
}
}
void BuildBusinessFeatures(SectionBuilder &builder) {
const auto session = builder.session();
const auto mtpOrder = session->appConfig().get<Order>(
"business_promo_order",
FallbackOrder());
const auto entryMap = EntryMap();
for (const auto &key : mtpOrder) {
const auto it = entryMap.find(key);
if (it == end(entryMap)) {
continue;
}
const auto feature = it->second.feature;
const auto id = FeatureSearchId(feature);
if (id.isEmpty()) {
continue;
}
builder.add(nullptr, [feature, id] {
return SearchEntry{
.id = id,
.title = FeatureSearchTitle(feature),
.keywords = { u"business"_q },
};
});
}
}
void BuildSponsoredSection(
SectionBuilder &builder,
QPointer<Ui::SettingsButton> *sponsoredButton) {
const auto controller = builder.controller();
const auto session = builder.session();
builder.add(nullptr, [] {
return SearchEntry{
.id = u"business/sponsored"_q,
.title = tr::lng_business_button_sponsored(tr::now),
.keywords = { u"ads"_q, u"advertising"_q },
};
});
if (!controller) {
return;
}
builder.add([controller, session, sponsoredButton](const WidgetContext &ctx) {
const auto content = ctx.container;
const auto sponsoredWrap = content->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
content,
object_ptr<Ui::VerticalLayout>(content)));
const auto fillSponsoredWrap = [=] {
while (sponsoredWrap->entity()->count()) {
delete sponsoredWrap->entity()->widgetAt(0);
}
Ui::AddDivider(sponsoredWrap->entity());
const auto loading = sponsoredWrap->entity()->add(
object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
sponsoredWrap->entity(),
object_ptr<Ui::FlatLabel>(
sponsoredWrap->entity(),
tr::lng_contacts_loading())),
st::boxRowPadding);
loading->entity()->setTextColorOverride(st::windowSubTextFg->c);
const auto wrap = sponsoredWrap->entity()->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
sponsoredWrap->entity(),
object_ptr<Ui::VerticalLayout>(sponsoredWrap->entity())));
wrap->toggle(false, anim::type::instant);
const auto inner = wrap->entity();
Ui::AddSkip(inner);
Ui::AddSubsectionTitle(
inner,
tr::lng_business_subtitle_sponsored());
const auto button = inner->add(object_ptr<Ui::SettingsButton>(
inner,
tr::lng_business_button_sponsored()));
if (sponsoredButton) {
*sponsoredButton = button;
}
Ui::AddSkip(inner);
{
inner->add(object_ptr<Ui::DividerLabel>(
inner,
object_ptr<Ui::FlatLabel>(
inner,
tr::lng_business_about_sponsored(
lt_link,
rpl::combine(
tr::lng_business_about_sponsored_link(
lt_emoji,
rpl::single(Ui::Text::IconEmoji(
&st::textMoreIconEmoji)),
tr::rich),
tr::lng_business_about_sponsored_url()
) | rpl::map([](TextWithEntities text, QString url) {
return tr::link(text, url);
}),
tr::rich),
st::boxDividerLabel),
st::defaultBoxDividerLabelPadding));
}
const auto api = inner->lifetime().make_state<Api::SponsoredToggle>(
session);
api->toggled(
) | rpl::on_next([=](bool enabled) {
button->toggleOn(rpl::single(enabled));
wrap->toggle(true, anim::type::instant);
loading->toggle(false, anim::type::instant);
button->toggledChanges(
) | rpl::on_next([=](bool toggled) {
api->setToggled(
toggled
) | rpl::on_error_done([=](const QString &error) {
controller->showToast(error);
}, [] {
}, button->lifetime());
}, button->lifetime());
}, inner->lifetime());
Ui::ToggleChildrenVisibility(sponsoredWrap->entity(), true);
sponsoredWrap->entity()->resizeToWidth(content->width());
};
Data::AmPremiumValue(session) | rpl::on_next([=](bool isPremium) {
sponsoredWrap->toggle(isPremium, anim::type::normal);
if (isPremium) {
fillSponsoredWrap();
}
}, content->lifetime());
return SectionBuilder::WidgetToAdd{};
});
}
void BuildBusinessSectionContent(
SectionBuilder &builder,
QPointer<Ui::SettingsButton> *sponsoredButton) {
const auto controller = builder.controller();
const auto session = builder.session();
if (controller) {
const auto owner = &session->data();
owner->chatbots().preload();
owner->businessInfo().preload();
owner->shortcutMessages().preloadShortcuts();
owner->session().api().chatLinks().preload();
}
builder.addSkip(st::settingsFromFileTop);
BuildBusinessFeatures(builder);
if (controller) {
builder.add([controller, session](const WidgetContext &ctx) {
const auto content = ctx.container;
const auto owner = &session->data();
const auto showOther = ctx.showOther;
auto waitingToShow = content->lifetime().make_state<PremiumFeature>(
PremiumFeature::Business);
auto setPaused = content->lifetime().make_state<Fn<void(bool)>>();
const auto showFeature = [=](PremiumFeature feature) {
if (feature == PremiumFeature::FilterTags) {
ShowPremiumPreviewToBuy(controller, feature);
return;
}
showOther([&] {
switch (feature) {
case PremiumFeature::AwayMessage: return AwayMessageId();
case PremiumFeature::BusinessHours: return WorkingHoursId();
case PremiumFeature::BusinessLocation: return LocationId();
case PremiumFeature::GreetingMessage: return GreetingId();
case PremiumFeature::QuickReplies: return QuickRepliesId();
case PremiumFeature::BusinessBots: return ChatbotsId();
case PremiumFeature::ChatIntro: return ChatIntroId();
case PremiumFeature::ChatLinks: return ChatLinksId();
}
Unexpected("Feature in showFeature.");
}());
};
const auto isReady = [=](PremiumFeature feature) {
switch (feature) {
case PremiumFeature::AwayMessage:
return owner->businessInfo().awaySettingsLoaded()
&& owner->shortcutMessages().shortcutsLoaded();
case PremiumFeature::BusinessHours:
return owner->session().user()->isFullLoaded()
&& owner->businessInfo().timezonesLoaded();
case PremiumFeature::BusinessLocation:
return owner->session().user()->isFullLoaded();
case PremiumFeature::GreetingMessage:
return owner->businessInfo().greetingSettingsLoaded()
&& owner->shortcutMessages().shortcutsLoaded();
case PremiumFeature::QuickReplies:
return owner->shortcutMessages().shortcutsLoaded();
case PremiumFeature::BusinessBots:
return owner->chatbots().loaded();
case PremiumFeature::ChatIntro:
return owner->session().user()->isFullLoaded();
case PremiumFeature::ChatLinks:
return owner->session().api().chatLinks().loaded();
case PremiumFeature::FilterTags:
return true;
}
Unexpected("Feature in isReady.");
};
const auto check = [=] {
if (*waitingToShow != PremiumFeature::Business
&& isReady(*waitingToShow)) {
showFeature(
std::exchange(*waitingToShow, PremiumFeature::Business));
}
};
rpl::merge(
owner->businessInfo().awaySettingsChanged(),
owner->businessInfo().greetingSettingsChanged(),
owner->businessInfo().timezonesValue() | rpl::to_empty,
owner->shortcutMessages().shortcutsChanged(),
owner->chatbots().changes() | rpl::to_empty,
owner->session().changes().peerUpdates(
owner->session().user(),
Data::PeerUpdate::Flag::FullInfo) | rpl::to_empty,
owner->session().api().chatLinks().loadedUpdates()
) | rpl::on_next(check, content->lifetime());
AddBusinessSummary(content, controller, [=](PremiumFeature feature) {
if (!session->premium()) {
if (*setPaused) {
(*setPaused)(true);
}
const auto hidden = crl::guard(content, [=] {
if (*setPaused) {
(*setPaused)(false);
}
});
ShowPremiumPreviewToBuy(controller, feature, hidden);
return;
} else if (!isReady(feature)) {
*waitingToShow = feature;
} else {
showFeature(feature);
}
});
return SectionBuilder::WidgetToAdd{};
});
}
BuildSponsoredSection(builder, sponsoredButton);
}
class Business : public Section<Business> {
public:
Business(
@@ -798,6 +1103,15 @@ base::weak_qptr<Ui::RpWidget> Business::createPinnedToBottom(
return base::make_weak(not_null<Ui::RpWidget*>{ content });
}
const auto kMeta = BuildHelper({
.id = Business::Id(),
.parentId = Main::Id(),
.title = &tr::lng_business_title,
.icon = &st::menuIconShop,
}, [](SectionBuilder &builder) {
BuildBusinessSectionContent(builder, nullptr);
});
} // namespace
template <>
@@ -865,4 +1179,9 @@ std::vector<PremiumFeature> BusinessFeaturesOrder(
}) | ranges::to_vector;
}
namespace Builder {
SectionBuildMethod BusinessSection = kMeta.build;
} // namespace Builder
} // namespace Settings
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "settings/settings_common.h"
#include "settings/settings_type.h"
enum class PremiumFeature;
@@ -28,4 +29,9 @@ void ShowBusiness(not_null<Window::SessionController*> controller);
[[nodiscard]] std::vector<PremiumFeature> BusinessFeaturesOrder(
not_null<::Main::Session*> session);
namespace Builder {
extern SectionBuildMethod BusinessSection;
} // namespace Builder
} // namespace Settings
@@ -42,7 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_builder.h"
#include "settings/cloud_password/settings_cloud_password_input.h"
#include "settings/sections/settings_advanced.h"
#include "settings/settings_business.h"
#include "settings/sections/settings_business.h"
#include "settings/sections/settings_calls.h"
#include "settings/sections/settings_chat.h"
#include "settings/settings_codes.h"