From 6baba5a7b2c6ae16dcdb3a1bca7189258981c415 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Wed, 20 Nov 2024 13:21:56 +0300 Subject: [PATCH] Added ability to disable chats filters tags from settings. --- Telegram/Resources/langs/lang.strings | 5 + .../SourceFiles/boxes/premium_preview_box.cpp | 5 + .../SourceFiles/boxes/premium_preview_box.h | 1 + .../SourceFiles/data/data_chat_filters.cpp | 17 ++ Telegram/SourceFiles/data/data_chat_filters.h | 2 + .../SourceFiles/settings/settings_folders.cpp | 172 ++++++++++++++++-- 6 files changed, 190 insertions(+), 12 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b9c61205a..20b1b7c6a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2300,6 +2300,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_summary_about_business" = "Upgrade your account with business features such as location, opening hours and quick replies."; "lng_premium_summary_subtitle_effects" = "Message Effects"; "lng_premium_summary_about_effects" = "Add over 500 animated effects to private messages."; +"lng_premium_summary_subtitle_filter_tags" = "Tag Your Chats"; +"lng_premium_summary_about_filter_tags" = "Display folder names for each chat in the chat list."; "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"; @@ -5155,6 +5157,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_filters_view_subtitle" = "Tabs view"; "lng_filters_vertical" = "Tabs on the left"; "lng_filters_horizontal" = "Tabs at the top"; +"lng_filters_enable_tags" = "Show Folder Tags"; +"lng_filters_enable_tags_about" = "Display folder names for each chat in the chat list."; +"lng_filters_enable_tags_about_premium" = "Subscribe to **{link}** to display folder names for each chat in the chat list."; "lng_filters_delete_sure" = "Are you sure you want to delete this folder? This will also deactivate all the invite links created to share this folder."; "lng_filters_link" = "Share Folder"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.cpp b/Telegram/SourceFiles/boxes/premium_preview_box.cpp index 89e0edfed..0e2bf6801 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/premium_preview_box.cpp @@ -133,6 +133,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_subtitle_business(); case PremiumFeature::Effects: return tr::lng_premium_summary_subtitle_effects(); + case PremiumFeature::FilterTags: + return tr::lng_premium_summary_subtitle_filter_tags(); case PremiumFeature::BusinessLocation: return tr::lng_business_subtitle_location(); @@ -196,6 +198,8 @@ void PreloadSticker(const std::shared_ptr &media) { return tr::lng_premium_summary_about_business(); case PremiumFeature::Effects: return tr::lng_premium_summary_about_effects(); + case PremiumFeature::FilterTags: + return tr::lng_premium_summary_about_filter_tags(); case PremiumFeature::BusinessLocation: return tr::lng_business_about_location(); @@ -534,6 +538,7 @@ struct VideoPreviewDocument { case PremiumFeature::LastSeen: return "last_seen"; case PremiumFeature::MessagePrivacy: return "message_privacy"; case PremiumFeature::Effects: return "effects"; + case PremiumFeature::FilterTags: return "folder_tags"; case PremiumFeature::BusinessLocation: return "business_location"; case PremiumFeature::BusinessHours: return "business_hours"; diff --git a/Telegram/SourceFiles/boxes/premium_preview_box.h b/Telegram/SourceFiles/boxes/premium_preview_box.h index 4dae5c30d..e631c9789 100644 --- a/Telegram/SourceFiles/boxes/premium_preview_box.h +++ b/Telegram/SourceFiles/boxes/premium_preview_box.h @@ -71,6 +71,7 @@ enum class PremiumFeature { MessagePrivacy, Business, Effects, + FilterTags, // Business features. BusinessLocation, diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index 535acc205..ba3c0758d 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -397,6 +397,23 @@ rpl::producer ChatFilters::tagsEnabledValue() const { return _tagsEnabled.value(); } +void ChatFilters::requestToggleTags(bool value, Fn fail) { + if (_toggleTagsRequestId) { + return; + } + _toggleTagsRequestId = _owner->session().api().request( + MTPmessages_ToggleDialogFilterTags(MTP_bool(value)) + ).done([=](const MTPBool &result) { + _tagsEnabled = value; + _toggleTagsRequestId = 0; + }).fail([=](const MTP::Error &error) { + const auto message = error.type(); + _toggleTagsRequestId = 0; + LOG(("API Error: Toggle Tags - %1").arg(message)); + fail(); + }).send(); +} + void ChatFilters::received(const QVector &list) { auto position = 0; auto changed = false; diff --git a/Telegram/SourceFiles/data/data_chat_filters.h b/Telegram/SourceFiles/data/data_chat_filters.h index 42a635809..18630b391 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.h +++ b/Telegram/SourceFiles/data/data_chat_filters.h @@ -188,6 +188,7 @@ public: [[nodiscard]] bool tagsEnabled() const; [[nodiscard]] rpl::producer tagsEnabledValue() const; + void requestToggleTags(bool value, Fn fail); private: struct MoreChatsData { @@ -217,6 +218,7 @@ private: mtpRequestId _loadRequestId = 0; mtpRequestId _saveOrderRequestId = 0; mtpRequestId _saveOrderAfterId = 0; + mtpRequestId _toggleTagsRequestId = 0; bool _loaded = false; bool _reloading = false; diff --git a/Telegram/SourceFiles/settings/settings_folders.cpp b/Telegram/SourceFiles/settings/settings_folders.cpp index 552215041..c0d595dd9 100644 --- a/Telegram/SourceFiles/settings/settings_folders.cpp +++ b/Telegram/SourceFiles/settings/settings_folders.cpp @@ -7,34 +7,38 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_folders.h" -#include "apiwrap.h" #include "api/api_chat_filters.h" // ProcessFilterRemove. -#include "boxes/premium_limits_box.h" +#include "apiwrap.h" #include "boxes/filters/edit_filter_box.h" +#include "boxes/premium_limits_box.h" +#include "boxes/premium_preview_box.h" #include "core/application.h" #include "data/data_chat_filters.h" #include "data/data_folder.h" #include "data/data_peer.h" #include "data/data_peer_values.h" // Data::AmPremiumValue. -#include "data/data_session.h" #include "data/data_premium_limits.h" +#include "data/data_session.h" #include "history/history.h" #include "lang/lang_keys.h" #include "lottie/lottie_icon.h" #include "main/main_session.h" +#include "settings/settings_premium.h" #include "ui/boxes/confirm_box.h" +#include "ui/empty_userpic.h" #include "ui/filter_icons.h" #include "ui/layers/generic_box.h" #include "ui/painter.h" -#include "ui/vertical_list.h" +#include "ui/rect.h" #include "ui/text/text_utilities.h" +#include "ui/ui_utility.h" +#include "ui/vertical_list.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/wrap/slide_wrap.h" -#include "ui/ui_utility.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_settings.h" @@ -67,6 +71,8 @@ public: [[nodiscard]] rpl::producer<> restoreRequests() const; [[nodiscard]] rpl::producer<> addRequests() const; + void setColorIndexProgress(float64 progress); + private: enum class State { Suggested, @@ -96,6 +102,8 @@ private: Ui::Text::String _title; QString _status; Ui::FilterIcon _icon = Ui::FilterIcon(); + std::optional _colorIndex; + float64 _colorIndexProgress = 1.; State _state = State::Normal; @@ -238,11 +246,11 @@ void FilterRowButton::setup( _title.setText(st::contactsNameStyle, filter.title()); _status = status; _icon = Ui::ComputeFilterIcon(filter); + _colorIndex = filter.colorIndex(); setState(_state, true); - sizeValue( - ) | rpl::start_with_next([=](QSize size) { + sizeValue() | rpl::start_with_next([=](QSize size) { const auto right = st::contactsPadding.right() + st::contactsCheckPosition.x(); const auto width = size.width(); @@ -272,6 +280,13 @@ rpl::producer<> FilterRowButton::addRequests() const { return _add.clicks() | rpl::to_empty; } +void FilterRowButton::setColorIndexProgress(float64 progress) { + _colorIndexProgress = progress; + if (_colorIndex) { + update(); + } +} + void FilterRowButton::paintEvent(QPaintEvent *e) { auto p = Painter(this); @@ -281,6 +296,19 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::windowBgOver); } RippleButton::paintRipple(p, 0, 0); + + if (_colorIndex) { + p.setPen(Qt::NoPen); + p.setBrush(Ui::EmptyUserpic::UserpicColor(*_colorIndex).color2); + const auto w = height() / 3; + const auto rect = QRect( + _remove.x() - w - st::contactsCheckPosition.x(), + (height() - w) / 2, + w, + w); + auto hq = PainterHighQualityEnabler(p); + p.drawEllipse(rect - Margins((1. - _colorIndexProgress) * w / 2)); + } } else if (_state == State::Removed) { p.setOpacity(st::stickersRowDisabledOpacity); } @@ -335,7 +363,8 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { [[nodiscard]] Fn SetupFoldersContent( not_null controller, - not_null container) { + not_null container, + not_null*> tagsButtonEnabled) { auto &lifetime = container->lifetime(); const auto weak = Ui::MakeWeak(container); @@ -351,6 +380,7 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { rpl::variable count; rpl::variable suggested; Fn)> save; + Ui::Animations::Simple tagsEnabledAnimation; }; const auto state = lifetime.make_state(); @@ -583,6 +613,22 @@ void FilterRowButton::paintEvent(QPaintEvent *e) { Ui::AddSkip(aboutRows); Ui::AddSubsectionTitle(aboutRows, tr::lng_filters_recommended()); + const auto setTagsProgress = [=](float64 value) { + for (const auto &row : state->rows) { + row.button->setColorIndexProgress(value); + } + }; + tagsButtonEnabled->events() | rpl::distinct_until_changed( + ) | rpl::start_with_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([=] { @@ -844,9 +890,101 @@ void SetupTopContent( } +void SetupTagContent( + not_null controller, + not_null content, + not_null*> tagsButtonEnabled) { + Ui::AddDivider(content); + Ui::AddSkip(content); + + const auto session = &controller->session(); + + struct State final { + rpl::event_stream tagsTurnOff; + base::Timer requestTimer; + Fn sendCallback; + }; + + auto premium = Data::AmPremiumValue(session); + const auto tagsButton = content->add( + object_ptr( + content, + tr::lng_filters_enable_tags(), + st::settingsButtonNoIconLocked)); + const auto state = tagsButton->lifetime().make_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::start_with_next([=](bool value) { + tagsButton->setToggleLocked(!value); + }, tagsButton->lifetime()); + + const auto send = [=, weak = Ui::MakeWeak(tagsButton)](bool checked) { + session->data().chatsFilters().requestToggleTags(checked, [=] { + if (const auto strong = weak.data()) { + state->tagsTurnOff.fire(!checked); + } + }); + }; + + 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::start_with_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(); + } + } + }); + + Ui::AddSkip(content); + const auto about = Ui::AddDividerText( + content, + rpl::conditional( + rpl::duplicate(premium), + tr::lng_filters_enable_tags_about(Ui::Text::RichLangValue), + tr::lng_filters_enable_tags_about_premium( + lt_link, + tr::lng_effect_premium_link() | rpl::map([](QString t) { + return Ui::Text::Link(std::move(t), u"internal:"_q); + }), + Ui::Text::RichLangValue))); + about->setClickHandlerFilter([=](const auto &...) { + Settings::ShowPremium(controller, u"folder_tags"_q); + return true; + }); +} + void SetupView( not_null controller, - not_null content) { + not_null content, + bool dividerNeeded) { const auto wrap = content->add( object_ptr>( content, @@ -854,7 +992,9 @@ void SetupView( wrap->toggleOn(controller->enoughSpaceForFiltersValue()); content = wrap->entity(); - Ui::AddDivider(content); + if (dividerNeeded) { + Ui::AddDivider(content); + } Ui::AddSkip(content); Ui::AddSubsectionTitle(content, tr::lng_filters_view_subtitle()); @@ -904,12 +1044,20 @@ void Folders::setupContent(not_null controller) { controller->session().data().chatsFilters().requestSuggested(); const auto content = Ui::CreateChild(this); + const auto tagsButtonEnabled + = content->lifetime().make_state>(); SetupTopContent(content, _showFinished.events()); - _save = SetupFoldersContent(controller, content); + _save = SetupFoldersContent(controller, content, tagsButtonEnabled); - SetupView(controller, content); + auto dividerNeeded = true; + if (controller->session().premiumPossible()) { + SetupTagContent(controller, content, tagsButtonEnabled); + dividerNeeded = false; + } + + SetupView(controller, content, dividerNeeded); Ui::ResizeFitChild(this, content); }