diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 64409ea88..fbfbb55f2 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1295,6 +1295,8 @@ PRIVATE settings/settings_advanced.h settings/settings_blocked_peers.cpp settings/settings_blocked_peers.h + settings/settings_business.cpp + settings/settings_business.h settings/settings_chat.cpp settings/settings_chat.h settings/settings_calls.cpp diff --git a/Telegram/Resources/art/business_logo.png b/Telegram/Resources/art/business_logo.png new file mode 100644 index 000000000..25c357e50 Binary files /dev/null and b/Telegram/Resources/art/business_logo.png differ diff --git a/Telegram/Resources/icons/menu/shop.png b/Telegram/Resources/icons/menu/shop.png new file mode 100644 index 000000000..80dfbc6f1 Binary files /dev/null and b/Telegram/Resources/icons/menu/shop.png differ diff --git a/Telegram/Resources/icons/menu/shop@2x.png b/Telegram/Resources/icons/menu/shop@2x.png new file mode 100644 index 000000000..38625e754 Binary files /dev/null and b/Telegram/Resources/icons/menu/shop@2x.png differ diff --git a/Telegram/Resources/icons/menu/shop@3x.png b/Telegram/Resources/icons/menu/shop@3x.png new file mode 100644 index 000000000..0da0b229c Binary files /dev/null and b/Telegram/Resources/icons/menu/shop@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_away.png b/Telegram/Resources/icons/settings/premium/business/business_away.png new file mode 100644 index 000000000..b6fe3bede Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_away.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_away@2x.png b/Telegram/Resources/icons/settings/premium/business/business_away@2x.png new file mode 100644 index 000000000..be8035562 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_away@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_away@3x.png b/Telegram/Resources/icons/settings/premium/business/business_away@3x.png new file mode 100644 index 000000000..59afa2748 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_away@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_chatbots.png b/Telegram/Resources/icons/settings/premium/business/business_chatbots.png new file mode 100644 index 000000000..aaa60e6a4 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_chatbots.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_chatbots@2x.png b/Telegram/Resources/icons/settings/premium/business/business_chatbots@2x.png new file mode 100644 index 000000000..e48940aaa Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_chatbots@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_chatbots@3x.png b/Telegram/Resources/icons/settings/premium/business/business_chatbots@3x.png new file mode 100644 index 000000000..2db7e80ea Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_chatbots@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_hours.png b/Telegram/Resources/icons/settings/premium/business/business_hours.png new file mode 100644 index 000000000..c6f0d03e3 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_hours.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_hours@2x.png b/Telegram/Resources/icons/settings/premium/business/business_hours@2x.png new file mode 100644 index 000000000..ce0984920 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_hours@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_hours@3x.png b/Telegram/Resources/icons/settings/premium/business/business_hours@3x.png new file mode 100644 index 000000000..0aee12c23 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_hours@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_location.png b/Telegram/Resources/icons/settings/premium/business/business_location.png new file mode 100644 index 000000000..ae7020aad Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_location.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_location@2x.png b/Telegram/Resources/icons/settings/premium/business/business_location@2x.png new file mode 100644 index 000000000..49b7ee3c8 Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_location@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_location@3x.png b/Telegram/Resources/icons/settings/premium/business/business_location@3x.png new file mode 100644 index 000000000..a442c069d Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_location@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_quick.png b/Telegram/Resources/icons/settings/premium/business/business_quick.png new file mode 100644 index 000000000..a9e7e1d1f Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_quick.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_quick@2x.png b/Telegram/Resources/icons/settings/premium/business/business_quick@2x.png new file mode 100644 index 000000000..3f4c1852e Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_quick@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/business/business_quick@3x.png b/Telegram/Resources/icons/settings/premium/business/business_quick@3x.png new file mode 100644 index 000000000..d927c704f Binary files /dev/null and b/Telegram/Resources/icons/settings/premium/business/business_quick@3x.png differ diff --git a/Telegram/Resources/icons/settings/premium/status.png b/Telegram/Resources/icons/settings/premium/status.png index fe1541145..712c91ba0 100644 Binary files a/Telegram/Resources/icons/settings/premium/status.png and b/Telegram/Resources/icons/settings/premium/status.png differ diff --git a/Telegram/Resources/icons/settings/premium/status@2x.png b/Telegram/Resources/icons/settings/premium/status@2x.png index d05da77b4..f0e395b4e 100644 Binary files a/Telegram/Resources/icons/settings/premium/status@2x.png and b/Telegram/Resources/icons/settings/premium/status@2x.png differ diff --git a/Telegram/Resources/icons/settings/premium/status@3x.png b/Telegram/Resources/icons/settings/premium/status@3x.png index 01052baef..9127319e9 100644 Binary files a/Telegram/Resources/icons/settings/premium/status@3x.png and b/Telegram/Resources/icons/settings/premium/status@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index ea6c5f2e8..8bded7e02 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2056,6 +2056,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_animated_userpics" = "Video avatars animated in chat lists and chats to allow for additional self-expression."; "lng_premium_summary_subtitle_translation" = "Real-Time Translation"; "lng_premium_summary_about_translation" = "Real-time translation of channels and chats into other languages."; +"lng_premium_summary_subtitle_business" = "Telegram Business"; +"lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies."; "lng_premium_summary_bottom_subtitle" = "About Telegram Premium"; "lng_premium_summary_bottom_about" = "While the free version of Telegram already gives its users more than any other messaging application, **Telegram Premium** pushes its capabilities even further.\n\n**Telegram Premium** is a paid option, because most Premium Features require additional expenses from Telegram to third parties such as data center providers and server manufacturers. Contributions from **Telegram Premium** users allow us to cover such costs and also help Telegram stay free for everyone."; "lng_premium_summary_button" = "Subscribe for {cost} per month"; @@ -2154,6 +2156,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_gifts_terms" = "By gifting Telegram Premium, you agree to the Telegram {link} and {policy}."; "lng_premium_gifts_terms_policy" = "Privacy Policy"; +"lng_business_title" = "Telegram Business"; +"lng_business_about" = "Turn your account to a business page with these additional features."; +"lng_business_unlocked" = "You have now unlocked these additional business features."; +"lng_business_subtitle_location" = "Location"; +"lng_business_about_location" = "Display the location of your business on your account."; +"lng_business_subtitle_opening_hours" = "Opening Hours"; +"lng_business_about_opening_hours" = "Show to your customers when you are open for business."; +"lng_business_subtitle_quick_replies" = "Quick Replies"; +"lng_business_about_quick_replies" = "Set up shortcuts up to 20 messages each to respond to customers faster."; +"lng_business_subtitle_greeting_messages" = "Greeting Messages"; +"lng_business_about_greeting_messages" = "Create greetings that will be automatically sent to new customers."; +"lng_business_subtitle_away_messages" = "Away Messages"; +"lng_business_about_away_messages" = "Define messages that are automatically sent when you are off."; +"lng_business_subtitle_chatbots" = "Chatbots"; +"lng_business_about_chatbots" = "Add any third party chatbots that will process customer interactions."; + "lng_boost_channel_button" = "Boost Channel"; "lng_boost_group_button" = "Boost Group"; "lng_boost_again_button" = "Boost Again"; diff --git a/Telegram/Resources/qrc/telegram/telegram.qrc b/Telegram/Resources/qrc/telegram/telegram.qrc index ccf1a1d7a..ae6edc957 100644 --- a/Telegram/Resources/qrc/telegram/telegram.qrc +++ b/Telegram/Resources/qrc/telegram/telegram.qrc @@ -3,6 +3,7 @@ ../../art/background.tgv ../../art/bg_thumbnail.png ../../art/bg_initial.jpg + ../../art/business_logo.png ../../art/logo_256.png ../../art/logo_256_no_margin.png ../../art/themeimage.jpg diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 93df20cfa..c3ce95189 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -33,6 +33,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/settings_premium.h" #include "lottie/lottie_single_player.h" #include "history/view/media/history_view_sticker.h" @@ -128,6 +129,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_subtitle_animated_userpics(); case PremiumPreview::RealTimeTranslation: return tr::lng_premium_summary_subtitle_translation(); + case PremiumPreview::Business: + return tr::lng_premium_summary_subtitle_business(); } Unexpected("PremiumPreview in SectionTitle."); } @@ -170,6 +173,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_animated_userpics(); case PremiumPreview::RealTimeTranslation: return tr::lng_premium_summary_about_translation(); + case PremiumPreview::Business: + return tr::lng_premium_summary_about_business(); } Unexpected("PremiumPreview in SectionTitle."); } @@ -1219,6 +1224,13 @@ void Show( DecorateListPromoBox(box, show, descriptor); })); return; + } else if (descriptor.section == PremiumPreview::Business) { + const auto window = show->resolveWindow( + ChatHelpers::WindowUsage::PremiumPromo); + if (window) { + Settings::ShowBusiness(window); + } + return; } auto &list = Preloads(); for (auto i = begin(list); i != end(list);) { diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index b7fb7a40e..9fc4da279 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -64,6 +64,7 @@ enum class PremiumPreview { TagsForMessages, LastSeen, MessagePrivacy, + Business, kCount, }; diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 1f17c1a7d..4b453c9b8 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -402,6 +402,7 @@ updateBotMessageReactions#9cb7759 peer:Peer msg_id:int date:int reactions:Vector updateSavedDialogPinned#aeaf9e74 flags:# pinned:flags.0?true peer:DialogPeer = Update; updatePinnedSavedDialogs#686c85a6 flags:# order:flags.0?Vector = Update; updateSavedReactionTags#39c67432 = Update; +updateSmsJob#f16269d4 job_id:string = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1653,6 +1654,12 @@ messages.savedReactionTags#3259950a tags:Vector hash:long = me outboxReadDate#3bb842ac date:int = OutboxReadDate; +smsjobs.eligibleToJoin#dc8b44cf terms_url:string monthly_sent_sms:int = smsjobs.EligibilityToJoin; + +smsjobs.status#2aee9191 flags:# allow_international:flags.0?true recent_sent:int recent_since:int recent_remains:int total_sent:int total_since:int last_gift_slug:flags.1?string terms_url:string = smsjobs.Status; + +smsJob#e6a1eeb8 job_id:string phone_number:string text:string = SmsJob; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2252,4 +2259,12 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 174 +smsjobs.isEligibleToJoin#edc39d0 = smsjobs.EligibilityToJoin; +smsjobs.join#a74ece2d = Bool; +smsjobs.leave#9898ad73 = Bool; +smsjobs.updateSettings#93fa0bf flags:# allow_international:flags.0?true = Bool; +smsjobs.getStatus#10a698e8 = smsjobs.Status; +smsjobs.getSmsJob#778d902f job_id:string = SmsJob; +smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; + +// LAYER 175 diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 9716a383e..eb6bcb360 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -94,6 +94,7 @@ settingsPremiumIconTranslations: icon {{ "settings/premium/translations", settin settingsPremiumIconTags: icon {{ "settings/premium/tags", settingsIconFg }}; settingsPremiumIconLastSeen: icon {{ "settings/premium/lastseen", settingsIconFg }}; settingsPremiumIconPrivacy: icon {{ "settings/premium/privacy", settingsIconFg }}; +settingsPremiumIconBusiness: icon {{ "settings/premium/privacy", settingsIconFg }}; settingsStoriesIconOrder: icon {{ "settings/premium/stories_order", premiumButtonBg1 }}; settingsStoriesIconStealth: icon {{ "menu/stealth", premiumButtonBg1 }}; @@ -103,6 +104,13 @@ settingsStoriesIconDownload: icon {{ "menu/download", premiumButtonBg1 }}; settingsStoriesIconCaption: icon {{ "settings/premium/stories_caption", premiumButtonBg1 }}; settingsStoriesIconLinks: icon {{ "menu/links_profile", premiumButtonBg1 }}; +settingsBusinessIconLocation: icon {{ "settings/premium/business/business_location", settingsIconFg }}; +settingsBusinessIconHours: icon {{ "settings/premium/business/business_hours", settingsIconFg }}; +settingsBusinessIconReplies: icon {{ "settings/premium/business/business_quick", settingsIconFg }}; +settingsBusinessIconGreeting: icon {{ "settings/premium/status", settingsIconFg }}; +settingsBusinessIconAway: icon {{ "settings/premium/business/business_away", settingsIconFg }}; +settingsBusinessIconChatbots: icon {{ "settings/premium/business/business_chatbots", settingsIconFg }}; + settingsPremiumNewBadge: FlatLabel(defaultFlatLabel) { style: TextStyle(semiboldTextStyle) { font: font(10px semibold); diff --git a/Telegram/SourceFiles/settings/settings_business.cpp b/Telegram/SourceFiles/settings/settings_business.cpp new file mode 100644 index 000000000..23a4d8914 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_business.cpp @@ -0,0 +1,568 @@ +/* +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_business.h" + +#include "boxes/premium_preview_box.h" +#include "core/click_handler_types.h" +#include "data/data_peer_values.h" // AmPremiumValue. +#include "info/info_wrap_widget.h" // Info::Wrap. +#include "info/settings/info_settings_widget.h" // SectionCustomTopBarData. +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "settings/settings_common_session.h" +#include "settings/settings_premium.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/text/text_utilities.h" +#include "ui/widgets/checkbox.h" // Ui::RadiobuttonGroup. +#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/vertical_list.h" +#include "window/window_session_controller.h" +#include "apiwrap.h" +#include "api/api_premium.h" +#include "styles/style_premium.h" +#include "styles/style_info.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace { + +struct Entry { + const style::icon *icon; + rpl::producer title; + rpl::producer description; + BusinessFeature feature = BusinessFeature::Location; +}; + +using Order = std::vector; + +[[nodiscard]] Order FallbackOrder() { + return Order{ + u"location"_q, + u"opening_hours"_q, + u"quick_replies"_q, + u"greeting_messages"_q, + u"away_messages"_q, + u"chatbots"_q, + }; +} + +[[nodiscard]] base::flat_map EntryMap() { + return base::flat_map{ + { + u"location"_q, + Entry{ + &st::settingsBusinessIconLocation, + tr::lng_business_subtitle_location(), + tr::lng_business_about_location(), + BusinessFeature::Location, + }, + }, + { + u"opening_hours"_q, + Entry{ + &st::settingsBusinessIconHours, + tr::lng_business_subtitle_opening_hours(), + tr::lng_business_about_opening_hours(), + BusinessFeature::OpeningHours, + }, + }, + { + u"quick_replies"_q, + Entry{ + &st::settingsBusinessIconReplies, + tr::lng_business_subtitle_quick_replies(), + tr::lng_business_about_quick_replies(), + BusinessFeature::QuickReplies, + }, + }, + { + u"greeting_messages"_q, + Entry{ + &st::settingsBusinessIconGreeting, + tr::lng_business_subtitle_greeting_messages(), + tr::lng_business_about_greeting_messages(), + BusinessFeature::GreetingMessages, + }, + }, + { + u"away_messages"_q, + Entry{ + &st::settingsBusinessIconAway, + tr::lng_business_subtitle_away_messages(), + tr::lng_business_about_away_messages(), + BusinessFeature::AwayMessages, + }, + }, + { + u"chatbots"_q, + Entry{ + &st::settingsBusinessIconChatbots, + tr::lng_business_subtitle_chatbots(), + tr::lng_business_about_chatbots(), + BusinessFeature::Chatbots, + }, + }, + }; +} + +void AddBusinessSummary( + not_null content, + not_null controller, + Fn buttonCallback) { + const auto &stDefault = st::settingsButton; + const auto &stLabel = st::defaultFlatLabel; + const auto iconSize = st::settingsPremiumIconDouble.size(); + const auto &titlePadding = st::settingsPremiumRowTitlePadding; + const auto &descriptionPadding = st::settingsPremiumRowAboutPadding; + + auto entryMap = EntryMap(); + auto iconContainers = std::vector(); + iconContainers.reserve(int(entryMap.size())); + + const auto addRow = [&](Entry &entry) { + const auto labelAscent = stLabel.style.font->ascent; + const auto button = Ui::CreateChild( + content.get(), + rpl::single(QString())); + + const auto label = content->add( + object_ptr( + content, + std::move(entry.title) | rpl::map(Ui::Text::Bold), + stLabel), + titlePadding); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + const auto description = content->add( + object_ptr( + content, + std::move(entry.description), + st::boxDividerLabel), + descriptionPadding); + description->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto dummy = Ui::CreateChild(content.get()); + dummy->setAttribute(Qt::WA_TransparentForMouseEvents); + + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + dummy->resize(s.width(), iconSize.height()); + }, dummy->lifetime()); + + label->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + dummy->moveToLeft(0, r.y() + (r.height() - labelAscent)); + }, dummy->lifetime()); + + rpl::combine( + content->widthValue(), + label->heightValue(), + description->heightValue() + ) | rpl::start_with_next([=, + topPadding = titlePadding, + bottomPadding = descriptionPadding]( + int width, + int topHeight, + int bottomHeight) { + button->resize( + width, + topPadding.top() + + topHeight + + topPadding.bottom() + + bottomPadding.top() + + bottomHeight + + bottomPadding.bottom()); + }, button->lifetime()); + label->topValue( + ) | rpl::start_with_next([=, padding = titlePadding.top()](int top) { + button->moveToLeft(0, top - padding); + }, button->lifetime()); + const auto arrow = Ui::CreateChild( + button, + st::backButton); + arrow->setIconOverride( + &st::settingsPremiumArrow, + &st::settingsPremiumArrowOver); + arrow->setAttribute(Qt::WA_TransparentForMouseEvents); + button->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + const auto &point = st::settingsPremiumArrowShift; + arrow->moveToRight( + -point.x(), + point.y() + (s.height() - arrow->height()) / 2); + }, arrow->lifetime()); + + const auto feature = entry.feature; + button->setClickedCallback([=] { buttonCallback(feature); }); + + iconContainers.push_back(dummy); + }; + + auto icons = std::vector(); + icons.reserve(int(entryMap.size())); + { + const auto &account = controller->session().account(); + const auto mtpOrder = FallbackOrder();/* session->account().appConfig().get( + "premium_promo_order", + FallbackOrder());*/ AssertIsDebug() + const auto processEntry = [&](Entry &entry) { + icons.push_back(entry.icon); + addRow(entry); + }; + + for (const auto &key : mtpOrder) { + auto it = entryMap.find(key); + if (it == end(entryMap)) { + continue; + } + processEntry(it->second); + } + } + + content->resizeToWidth(content->height()); + + // Icons. + Assert(iconContainers.size() > 2); + const auto from = iconContainers.front()->y(); + const auto to = iconContainers.back()->y() + iconSize.height(); + auto gradient = QLinearGradient(0, 0, 0, to - from); + gradient.setStops(Ui::Premium::FullHeightGradientStops()); + for (auto i = 0; i < int(icons.size()); i++) { + const auto &iconContainer = iconContainers[i]; + + const auto pointTop = iconContainer->y() - from; + const auto pointBottom = pointTop + iconContainer->height(); + const auto ratioTop = pointTop / float64(to - from); + const auto ratioBottom = pointBottom / float64(to - from); + + auto resultGradient = QLinearGradient( + QPointF(), + QPointF(0, pointBottom - pointTop)); + + resultGradient.setColorAt( + .0, + anim::gradient_color_at(gradient, ratioTop)); + resultGradient.setColorAt( + .1, + anim::gradient_color_at(gradient, ratioBottom)); + + const auto brush = QBrush(resultGradient); + AddButtonIcon( + iconContainer, + stDefault, + { .icon = icons[i], .backgroundBrush = brush }); + } + + Ui::AddSkip(content, descriptionPadding.bottom()); +} + +class Business : public Section { +public: + Business( + QWidget *parent, + not_null controller); + + [[nodiscard]] rpl::producer title() override; + + [[nodiscard]] QPointer createPinnedToTop( + not_null parent) override; + [[nodiscard]] QPointer createPinnedToBottom( + not_null parent) override; + + void showFinished() override; + + [[nodiscard]] bool hasFlexibleTopBar() const override; + + void setStepDataReference(std::any &data) override; + + [[nodiscard]] rpl::producer<> sectionShowBack() override final; + +private: + void setupContent(); + + const not_null _controller; + + QPointer _subscribe; + base::unique_qptr> _back; + base::unique_qptr _close; + rpl::variable _backToggles; + rpl::variable _wrap; + Fn _setPaused; + + std::shared_ptr _radioGroup; + + rpl::event_stream<> _showBack; + rpl::event_stream<> _showFinished; + rpl::variable _buttonText; + +}; + +Business::Business( + QWidget *parent, + not_null controller) +: Section(parent) +, _controller(controller) +, _radioGroup(std::make_shared()) { + setupContent(); + _controller->session().api().premium().reload(); +} + +rpl::producer Business::title() { + return tr::lng_premium_summary_title(); +} + +bool Business::hasFlexibleTopBar() const { + return true; +} + +rpl::producer<> Business::sectionShowBack() { + return _showBack.events(); +} + +void Business::setStepDataReference(std::any &data) { + using namespace Info::Settings; + const auto my = std::any_cast(&data); + if (my) { + _backToggles = std::move( + my->backButtonEnables + ) | rpl::map_to(true); + _wrap = std::move(my->wrapValue); + } +} + +void Business::setupContent() { + const auto content = Ui::CreateChild(this); + + Ui::AddSkip(content, st::settingsFromFileTop); + + AddBusinessSummary(content, _controller, [=](BusinessFeature feature) { + }); + + Ui::ResizeFitChild(this, content); +} + +QPointer Business::createPinnedToTop( + not_null parent) { + auto title = tr::lng_business_title(); + auto about = [&]() -> rpl::producer { + return rpl::conditional( + Data::AmPremiumValue(&_controller->session()), + tr::lng_business_unlocked(), + tr::lng_business_about() + ) | Ui::Text::ToWithEntities(); + }(); + + const auto content = [&]() -> Ui::Premium::TopBarAbstract* { + const auto weak = base::make_weak(_controller); + const auto clickContextOther = [=] { + return QVariant::fromValue(ClickHandlerContext{ + .sessionWindow = weak, + .botStartAutoSubmit = true, + }); + }; + return Ui::CreateChild( + parent.get(), + st::defaultPremiumCover, + Ui::Premium::TopBarDescriptor{ + .clickContextOther = clickContextOther, + .logo = u"dollar"_q, + .title = std::move(title), + .about = std::move(about), + }); + }(); + _setPaused = [=](bool paused) { + content->setPaused(paused); + if (_subscribe) { + _subscribe->setGlarePaused(paused); + } + }; + + _wrap.value( + ) | rpl::start_with_next([=](Info::Wrap wrap) { + content->setRoundEdges(wrap == Info::Wrap::Layer); + }, content->lifetime()); + + const auto calculateMaximumHeight = [=] { + return st::settingsPremiumTopHeight; + }; + + content->setMaximumHeight(calculateMaximumHeight()); + content->setMinimumHeight(st::settingsPremiumTopHeight);// st::infoLayerTopBarHeight); + + content->resize(content->width(), content->maximumHeight()); + //content->additionalHeight( + //) | rpl::start_with_next([=](int additionalHeight) { + // const auto wasMax = (content->height() == content->maximumHeight()); + // content->setMaximumHeight(calculateMaximumHeight() + // + additionalHeight); + // if (wasMax) { + // content->resize(content->width(), content->maximumHeight()); + // } + //}, content->lifetime()); + + _wrap.value( + ) | rpl::start_with_next([=](Info::Wrap wrap) { + const auto isLayer = (wrap == Info::Wrap::Layer); + _back = base::make_unique_q>( + content, + object_ptr( + content, + (isLayer + ? st::settingsPremiumLayerTopBarBack + : st::settingsPremiumTopBarBack)), + st::infoTopBarScale); + _back->setDuration(0); + _back->toggleOn(isLayer + ? _backToggles.value() | rpl::type_erased() + : rpl::single(true)); + _back->entity()->addClickHandler([=] { + _showBack.fire({}); + }); + _back->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + const auto &st = isLayer ? st::infoLayerTopBar : st::infoTopBar; + content->setTextPosition( + toggled ? st.back.width : st.titlePosition.x(), + st.titlePosition.y()); + }, _back->lifetime()); + + if (!isLayer) { + _close = nullptr; + } else { + _close = base::make_unique_q( + content, + st::settingsPremiumTopBarClose); + _close->addClickHandler([=] { + _controller->parentController()->hideLayer(); + _controller->parentController()->hideSpecialLayer(); + }); + content->widthValue( + ) | rpl::start_with_next([=] { + _close->moveToRight(0, 0); + }, _close->lifetime()); + } + }, content->lifetime()); + + return Ui::MakeWeak(not_null{ content }); +} + +void Business::showFinished() { + _showFinished.fire({}); +} + +QPointer Business::createPinnedToBottom( + not_null parent) { + const auto content = Ui::CreateChild(parent.get()); + + const auto session = &_controller->session(); + + auto buttonText = _buttonText.value(); + + _subscribe = CreateSubscribeButton({ + _controller, + content, + [] { return u"business"_q; }, + std::move(buttonText), + std::nullopt, + [=, options = session->api().premium().subscriptionOptions()] { + const auto value = _radioGroup->value(); + return (value < options.size() && value >= 0) + ? options[value].botUrl + : QString(); + }, + }); + { + const auto callback = [=](int value) { + const auto options = + _controller->session().api().premium().subscriptionOptions(); + if (options.empty()) { + return; + } + Assert(value < options.size() && value >= 0); + auto text = tr::lng_premium_subscribe_button( + tr::now, + lt_cost, + options[value].costPerMonth); + _buttonText = std::move(text); + }; + _radioGroup->setChangedCallback(callback); + callback(0); + } + + _showFinished.events( + ) | rpl::take(1) | rpl::start_with_next([=] { + _subscribe->startGlareAnimation(); + }, _subscribe->lifetime()); + + content->widthValue( + ) | rpl::start_with_next([=](int width) { + const auto padding = st::settingsPremiumButtonPadding; + _subscribe->resizeToWidth(width - padding.left() - padding.right()); + }, _subscribe->lifetime()); + + rpl::combine( + _subscribe->heightValue(), + Data::AmPremiumValue(session), + session->premiumPossibleValue() + ) | rpl::start_with_next([=]( + int buttonHeight, + bool premium, + bool premiumPossible) { + const auto padding = st::settingsPremiumButtonPadding; + const auto finalHeight = !premiumPossible + ? 0 + : !premium + ? (padding.top() + buttonHeight + padding.bottom()) + : 0; + content->resize(content->width(), finalHeight); + _subscribe->moveToLeft(padding.left(), padding.top()); + _subscribe->setVisible(!premium && premiumPossible); + }, _subscribe->lifetime()); + + return Ui::MakeWeak(not_null{ content }); +} + +} // namespace + +template <> +struct SectionFactory : AbstractSectionFactory { + object_ptr create( + not_null parent, + not_null controller + ) const final override { + return object_ptr(parent, controller); + } + bool hasCustomTopBar() const final override { + return true; + } + + [[nodiscard]] static const std::shared_ptr &Instance() { + static const auto result = std::make_shared(); + return result; + } +}; + +Type BusinessId() { + return Business::Id(); +} + +void ShowBusiness(not_null controller) { + if (!controller->session().premiumPossible()) { + controller->show(Box(PremiumUnavailableBox)); + return; + } + controller->showSettings(Settings::BusinessId()); +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_business.h b/Telegram/SourceFiles/settings/settings_business.h new file mode 100644 index 000000000..e255fd715 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_business.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "settings/settings_type.h" + +namespace Main { +class Session; +} // namespace Main + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings { + +enum class BusinessFeature { + Location, + OpeningHours, + QuickReplies, + GreetingMessages, + AwayMessages, + Chatbots, + + kCount, +}; + +[[nodiscard]] Type BusinessId(); + +void ShowBusiness(not_null controller); + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_main.cpp b/Telegram/SourceFiles/settings/settings_main.cpp index 1b5f8304b..854c94b05 100644 --- a/Telegram/SourceFiles/settings/settings_main.cpp +++ b/Telegram/SourceFiles/settings/settings_main.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_main.h" +#include "settings/settings_business.h" #include "settings/settings_codes.h" #include "settings/settings_chat.h" #include "settings/settings_information.h" @@ -419,6 +420,19 @@ void SetupPremium( controller->setPremiumRef("settings"); showOther(PremiumId()); }); + const auto button = AddButtonWithIcon( + container, + tr::lng_business_title(), + st::settingsButton, + { .icon = &st::menuIconShop }); + button->addClickHandler([=] { + showOther(BusinessId()); + }); + constexpr auto kNewExpiresAt = int(1711958400); + if (base::unixtime::now() < kNewExpiresAt) { + Ui::NewBadge::AddToRight(button); + } + if (controller->session().premiumCanBuy()) { const auto button = AddButtonWithIcon( container, diff --git a/Telegram/SourceFiles/settings/settings_premium.cpp b/Telegram/SourceFiles/settings/settings_premium.cpp index cde74eb5a..f9d627d3b 100644 --- a/Telegram/SourceFiles/settings/settings_premium.cpp +++ b/Telegram/SourceFiles/settings/settings_premium.cpp @@ -180,6 +180,7 @@ using Order = std::vector; u"stories"_q, u"more_upload"_q, u"double_limits"_q, + u"business"_q, u"last_seen"_q, u"voice_to_text"_q, u"faster_download"_q, @@ -367,6 +368,16 @@ using Order = std::vector; PremiumPreview::RealTimeTranslation, }, }, + { + u"business"_q, + Entry{ + &st::settingsPremiumIconPlay, AssertIsDebug() + tr::lng_premium_summary_subtitle_business(), + tr::lng_premium_summary_about_business(), + PremiumPreview::Business, + true, + }, + }, }; } @@ -1671,9 +1682,9 @@ void AddSummaryPremium( icons.reserve(int(entryMap.size())); { const auto &account = controller->session().account(); - const auto mtpOrder = account.appConfig().get( + const auto mtpOrder = FallbackOrder();/* session->account().appConfig().get( "premium_promo_order", - FallbackOrder()); + FallbackOrder());*/ AssertIsDebug() const auto processEntry = [&](Entry &entry) { icons.push_back(entry.icon); addRow(entry); diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp index 4094b584d..5ef11ba50 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.cpp @@ -25,6 +25,21 @@ constexpr auto kBodyAnimationPart = 0.90; constexpr auto kTitleAdditionalScale = 0.15; constexpr auto kMinAcceptableContrast = 4.5; // 1.14; +[[nodiscard]] QImage ScaleTo(QImage image) { + using namespace style; + const auto size = image.size(); + const auto scale = DevicePixelRatio() * Scale() / 300.; + const auto scaled = QSize( + int(base::SafeRound(size.width() * scale)), + int(base::SafeRound(size.height() * scale))); + image = image.scaled( + scaled, + Qt::IgnoreAspectRatio, + Qt::SmoothTransformation); + image.setDevicePixelRatio(DevicePixelRatio()); + return image; +} + } // namespace QString Svg() { @@ -157,27 +172,41 @@ TopBar::TopBar( rpl::producer about, bool light, bool optimizeMinistars) +: TopBar(parent, st, { + .clickContextOther = std::move(clickContextOther), + .title = std::move(title), + .about = std::move(about), + .light = light, + .optimizeMinistars = optimizeMinistars, +}) { +} + +TopBar::TopBar( + not_null parent, + const style::PremiumCover &st, + TopBarDescriptor &&descriptor) : TopBarAbstract(parent, st) -, _light(light) +, _light(descriptor.light) +, _logo(descriptor.logo) , _titleFont(st.titleFont) , _titlePadding(st.titlePadding) -, _about(this, std::move(about), st.about) -, _ministars(this, optimizeMinistars) { +, _about(this, std::move(descriptor.about), st.about) +, _ministars(this, descriptor.optimizeMinistars) { std::move( - title + descriptor.title ) | rpl::start_with_next([=](QString text) { _titlePath = QPainterPath(); _titlePath.addText(0, _titleFont->ascent, _titleFont, text); update(); }, lifetime()); - if (clickContextOther) { + if (const auto other = descriptor.clickContextOther) { _about->setClickHandlerFilter([=]( const ClickHandlerPtr &handler, Qt::MouseButton button) { ActivateClickHandler(_about, handler, { button, - clickContextOther() + other() }); return false; }); @@ -188,7 +217,10 @@ TopBar::TopBar( ) | rpl::start_with_next([=] { TopBarAbstract::computeIsDark(); - if (!_light && !TopBarAbstract::isDark()) { + if (_logo == u"dollar"_q) { + _dollar = ScaleTo(QImage(u":/gui/art/business_logo.png"_q)); + _ministars.setColorOverride(st::premiumButtonFg->c); + } else if (!_light && !TopBarAbstract::isDark()) { _star.load(Svg()); _ministars.setColorOverride(st::premiumButtonFg->c); } else { @@ -232,8 +264,11 @@ rpl::producer TopBar::additionalHeight() const { } void TopBar::resizeEvent(QResizeEvent *e) { - const auto progress = (e->size().height() - minimumHeight()) - / float64(maximumHeight() - minimumHeight()); + const auto max = maximumHeight(); + const auto min = minimumHeight(); + const auto progress = (max > min) + ? ((e->size().height() - min) / float64(max - min)) + : 1.; _progress.top = 1. - std::clamp( (1. - progress) / kBodyAnimationPart, @@ -291,7 +326,12 @@ void TopBar::paintEvent(QPaintEvent *e) { } p.resetTransform(); - _star.render(&p, _starRect); + if (!_dollar.isNull()) { + auto hq = PainterHighQualityEnabler(p); + p.drawImage(_starRect, _dollar); + } else { + _star.render(&p, _starRect); + } const auto color = _light ? st::settingsPremiumUserTitle.textFg diff --git a/Telegram/SourceFiles/ui/effects/premium_top_bar.h b/Telegram/SourceFiles/ui/effects/premium_top_bar.h index 9ccc4fa30..4ac3cc8be 100644 --- a/Telegram/SourceFiles/ui/effects/premium_top_bar.h +++ b/Telegram/SourceFiles/ui/effects/premium_top_bar.h @@ -64,6 +64,15 @@ private: }; +struct TopBarDescriptor { + Fn clickContextOther; + QString logo; + rpl::producer title; + rpl::producer about; + bool light = false; + bool optimizeMinistars = true; +}; + class TopBar final : public TopBarAbstract { public: TopBar( @@ -74,6 +83,10 @@ public: rpl::producer about, bool light = false, bool optimizeMinistars = true); + TopBar( + not_null parent, + const style::PremiumCover &st, + TopBarDescriptor &&descriptor); ~TopBar(); void setPaused(bool paused) override; @@ -87,11 +100,13 @@ protected: private: const bool _light = false; + const QString _logo; const style::font &_titleFont; const style::margins &_titlePadding; object_ptr _about; ColoredMiniStars _ministars; QSvgRenderer _star; + QImage _dollar; struct { float64 top = 0.; diff --git a/Telegram/SourceFiles/ui/menu_icons.style b/Telegram/SourceFiles/ui/menu_icons.style index b0e0dda13..60fa215bf 100644 --- a/Telegram/SourceFiles/ui/menu_icons.style +++ b/Telegram/SourceFiles/ui/menu_icons.style @@ -137,6 +137,7 @@ menuIconAntispam: icon {{ "menu/antispam", menuIconColor }}; menuIconChatDiscuss: icon {{ "menu/chat_discuss", menuIconColor }}; menuIconBotCommands: icon {{ "menu/bot_commands", menuIconColor }}; menuIconPremium: icon {{ "menu/premium", menuIconColor }}; +menuIconShop: icon {{ "menu/shop", menuIconColor }}; menuIconIpAddress: icon {{ "menu/ip_address", menuIconColor }}; menuIconAddress: icon {{ "menu/payment_address", menuIconColor }}; menuIconShowAll: icon {{ "menu/all_media", menuIconColor }};