diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 237c2bb0b..5cf7685a9 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1173,6 +1173,8 @@ PRIVATE settings/settings_experimental.h settings/settings_folders.cpp settings/settings_folders.h + settings/settings_global_ttl.cpp + settings/settings_global_ttl.h settings/settings_information.cpp settings/settings_information.h settings/settings_intro.cpp diff --git a/Telegram/Resources/animations/ttl.tgs b/Telegram/Resources/animations/ttl.tgs new file mode 100644 index 000000000..14c228a7f Binary files /dev/null and b/Telegram/Resources/animations/ttl.tgs differ diff --git a/Telegram/Resources/icons/settings/ttl.png b/Telegram/Resources/icons/settings/ttl.png new file mode 100644 index 000000000..a8263fe2e Binary files /dev/null and b/Telegram/Resources/icons/settings/ttl.png differ diff --git a/Telegram/Resources/icons/settings/ttl@2x.png b/Telegram/Resources/icons/settings/ttl@2x.png new file mode 100644 index 000000000..01a63ec35 Binary files /dev/null and b/Telegram/Resources/icons/settings/ttl@2x.png differ diff --git a/Telegram/Resources/icons/settings/ttl@3x.png b/Telegram/Resources/icons/settings/ttl@3x.png new file mode 100644 index 000000000..a80936938 Binary files /dev/null and b/Telegram/Resources/icons/settings/ttl@3x.png differ diff --git a/Telegram/Resources/qrc/telegram/animations.qrc b/Telegram/Resources/qrc/telegram/animations.qrc index 4197f473f..5fcb2dd18 100644 --- a/Telegram/Resources/qrc/telegram/animations.qrc +++ b/Telegram/Resources/qrc/telegram/animations.qrc @@ -8,5 +8,6 @@ ../../animations/cloud_password/password_input.tgs ../../animations/cloud_password/hint.tgs ../../animations/cloud_password/email.tgs + ../../animations/ttl.tgs diff --git a/Telegram/SourceFiles/menu/menu_ttl.cpp b/Telegram/SourceFiles/menu/menu_ttl.cpp index 0b2eb509d..ca27d0f63 100644 --- a/Telegram/SourceFiles/menu/menu_ttl.cpp +++ b/Telegram/SourceFiles/menu/menu_ttl.cpp @@ -158,10 +158,12 @@ void TTLBoxOld( } // namespace void TTLBox(not_null box, Args args) { - box->addRow(object_ptr( - box, - std::move(args.about), - st::boxLabel)); + if (args.about) { + box->addRow(object_ptr( + box, + std::move(args.about), + st::boxLabel)); + } const auto ttls = std::vector{ (86400 * 1), @@ -188,17 +190,14 @@ void TTLBox(not_null box, Args args) { const auto pickerTtl = TimePickerBox(box, ttls, phrases, args.startTtl); Ui::ConfirmBox(box, { - .confirmed = [=] { - args.callback(pickerTtl()); - box->getDelegate()->hideLayer(); - }, + .confirmed = [=] { args.callback(pickerTtl()); }, .confirmText = tr::lng_settings_save(), .cancelText = tr::lng_cancel(), }); box->setTitle(tr::lng_manage_messages_ttl_title()); - if (args.startTtl) { + if (args.startTtl && !args.hideDisable) { box->addLeftButton(tr::lng_manage_messages_ttl_disable(), [=] { args.callback(0); box->getDelegate()->hideLayer(); diff --git a/Telegram/SourceFiles/menu/menu_ttl.h b/Telegram/SourceFiles/menu/menu_ttl.h index e38ec8f0c..8d92453ec 100644 --- a/Telegram/SourceFiles/menu/menu_ttl.h +++ b/Telegram/SourceFiles/menu/menu_ttl.h @@ -23,6 +23,7 @@ struct Args { TimeId startTtl; rpl::producer about; Fn callback; + bool hideDisable = false; }; void TTLBox(not_null box, Args args); diff --git a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp index cd1e70e75..8fc263115 100644 --- a/Telegram/SourceFiles/menu/menu_ttl_validator.cpp +++ b/Telegram/SourceFiles/menu/menu_ttl_validator.cpp @@ -92,6 +92,7 @@ Args TTLValidator::createArgs() const { }).fail([=] { state->savingRequestId = 0; }).send(); + show->hideLayer(); }; auto about = peer->isUser() ? tr::lng_ttl_edit_about(lt_user, rpl::single(peer->shortName())) diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index c0345e362..2fa8252ce 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -93,6 +93,7 @@ settingsIconPin: icon {{ "settings/pin", settingsIconFg }}; settingsIconDownload: icon {{ "settings/download", settingsIconFg }}; settingsIconMention: icon {{ "settings/mention", settingsIconFg }}; settingsIconTopics: icon {{ "settings/topics", settingsIconFg }}; +settingsIconTTL: icon {{ "settings/ttl", settingsIconFg }}; settingsPremiumIconChannelsOff: icon {{ "settings/premium/channels_off", settingsIconFg }}; settingsPremiumIconDouble: icon {{ "settings/premium/double", settingsIconFg }}; diff --git a/Telegram/SourceFiles/settings/settings_global_ttl.cpp b/Telegram/SourceFiles/settings/settings_global_ttl.cpp new file mode 100644 index 000000000..aef9a8407 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_global_ttl.cpp @@ -0,0 +1,273 @@ +/* +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_global_ttl.h" + +#include "api/api_self_destruct.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "lottie/lottie_icon.h" +#include "main/main_session.h" +#include "menu/menu_ttl.h" +#include "settings/settings_common.h" +#include "ui/boxes/confirm_box.h" +#include "ui/text/format_values.h" +#include "ui/text/text_utilities.h" +#include "ui/toasts/common_toasts.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_layers.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace { + +void SetupTopContent( + not_null parent, + rpl::producer<> showFinished) { + const auto divider = Ui::CreateChild(parent.get()); + const auto verticalLayout = parent->add( + object_ptr(parent.get())); + + auto icon = CreateLottieIcon( + verticalLayout, + { + .name = u"ttl"_q, + .sizeOverride = { + st::settingsCloudPasswordIconSize, + st::settingsCloudPasswordIconSize, + }, + }, + st::settingsFilterIconPadding); + std::move( + showFinished + ) | rpl::start_with_next([animate = std::move(icon.animate)] { + animate(anim::repeat::loop); + }, verticalLayout->lifetime()); + verticalLayout->add(std::move(icon.widget)); + + verticalLayout->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + divider->setGeometry(r); + }, divider->lifetime()); + +} + +} // namespace + +class GlobalTTL : public Section { +public: + GlobalTTL( + QWidget *parent, + not_null controller); + + [[nodiscard]] rpl::producer title() override; + void setupContent(); + + void showFinished() override final; + +private: + void rebuildButtons(TimeId currentTTL) const; + void showSure(TimeId ttl, bool rebuild) const; + + void request(TimeId ttl) const; + + const not_null _controller; + const std::shared_ptr _group; + const std::shared_ptr _show; + + not_null _buttons; + + rpl::event_stream<> _showFinished; + rpl::lifetime _requestLifetime; + +}; + +GlobalTTL::GlobalTTL( + QWidget *parent, + not_null controller) +: Section(parent) +, _controller(controller) +, _group(std::make_shared(0)) +, _show(std::make_shared(controller)) +, _buttons(Ui::CreateChild(this)) { + setupContent(); +} + +rpl::producer GlobalTTL::title() { + return tr::lng_settings_ttl_title(); +} + +void GlobalTTL::request(TimeId ttl) const { + _controller->session().api().selfDestruct().updateDefaultHistoryTTL(ttl); +} + +void GlobalTTL::showSure(TimeId ttl, bool rebuild) const { + const auto ttlText = Ui::FormatTTLAfter(ttl); + const auto confirmed = [=] { + if (rebuild) { + rebuildButtons(ttl); + } + _group->setChangedCallback([=](int value) { + _group->setChangedCallback(nullptr); + Ui::ShowMultilineToast({ + .parentOverride = _show->toastParent(), + .text = tr::lng_settings_ttl_after_toast( + tr::now, + lt_after_duration, + { .text = ttlText }, + Ui::Text::WithEntities) + }); + _show->hideLayer(); // Don't use close(). + }); + request(ttl); + }; + if (_group->value()) { + confirmed(); + return; + } + _show->showBox(Ui::MakeConfirmBox({ + .text = tr::lng_settings_ttl_after_sure( + lt_after_duration, + rpl::single(ttlText)), + .confirmed = confirmed, + .cancelled = [=](Fn &&close) { + _group->setChangedCallback(nullptr); + close(); + }, + .confirmText = tr::lng_sure_enable(), + })); +} + +void GlobalTTL::rebuildButtons(TimeId currentTTL) const { + auto ttls = std::vector{ + 0, + 3600 * 24, + 3600 * 24 * 7, + 3600 * 24 * 31, + }; + if (!ranges::contains(ttls, currentTTL)) { + ttls.push_back(currentTTL); + ranges::sort(ttls); + } + if (_buttons->count() > ttls.size()) { + return; + } + _buttons->clear(); + for (const auto &ttl : ttls) { + const auto ttlText = Ui::FormatTTLAfter(ttl); + const auto button = AddButton( + _buttons, + (!ttl) + ? tr::lng_settings_ttl_after_off() + : tr::lng_settings_ttl_after( + lt_after_duration, + rpl::single(ttlText)), + st::settingsButtonNoIcon); + button->setClickedCallback([=] { + if (_group->value() == ttl) { + return; + } + if (!ttl) { + _group->setChangedCallback(nullptr); + request(ttl); + return; + } + showSure(ttl, false); + }); + const auto radio = Ui::CreateChild( + button.get(), + _group, + ttl, + QString()); + radio->setAttribute(Qt::WA_TransparentForMouseEvents); + radio->show(); + button->sizeValue( + ) | rpl::start_with_next([=] { + radio->moveToRight(0, radio->checkRect().top()); + }, radio->lifetime()); + } + _buttons->resizeToWidth(width()); +} + +void GlobalTTL::setupContent() { + setFocusPolicy(Qt::StrongFocus); + setFocus(); + + const auto content = Ui::CreateChild(this); + + SetupTopContent(content, _showFinished.events()); + + AddSkip(content); + AddSubsectionTitle(content, tr::lng_settings_ttl_after_subtitle()); + + content->add(object_ptr::fromRaw(_buttons)); + + { + const auto &apiTTL = _controller->session().api().selfDestruct(); + const auto rebuild = [=](TimeId period) { + rebuildButtons(period); + _group->setValue(period); + }; + rebuild(apiTTL.periodDefaultHistoryTTLCurrent()); + apiTTL.periodDefaultHistoryTTL( + ) | rpl::start_with_next(rebuild, content->lifetime()); + } + + const auto show = std::make_shared(_controller); + AddButton( + content, + tr::lng_settings_ttl_after_custom(), + st::settingsButtonNoIcon)->setClickedCallback([=] { + struct Args { + std::shared_ptr show; + TimeId startTtl; + rpl::producer about; + Fn callback; + }; + + show->showBox(Box(TTLMenu::TTLBox, TTLMenu::Args{ + .show = show, + .startTtl = _group->value(), + .callback = [=](TimeId ttl) { showSure(ttl, true); }, + .hideDisable = true, + })); + }); + + AddSkip(content); + + auto footer = object_ptr( + content, + tr::lng_settings_ttl_after_about( + lt_link, + tr::lng_settings_ttl_after_about_link( + ) | rpl::map([](QString s) { return Ui::Text::Link(s, 1); }), + Ui::Text::WithEntities), + st::boxDividerLabel); + footer->overrideLinkClickHandler([=] { + }); + content->add(object_ptr( + content, + std::move(footer), + st::settingsDividerLabelPadding)); + + Ui::ResizeFitChild(this, content); +} + +void GlobalTTL::showFinished() { + _showFinished.fire({}); +} + +Type GlobalTTLId() { + return GlobalTTL::Id(); +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_global_ttl.h b/Telegram/SourceFiles/settings/settings_global_ttl.h new file mode 100644 index 000000000..a97c7491b --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_global_ttl.h @@ -0,0 +1,17 @@ +/* +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 Settings { + +Type GlobalTTLId(); + +} // namespace Settings + diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 7d51d969e..f4c270808 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/cloud_password/settings_cloud_password_start.h" #include "settings/settings_blocked_peers.h" #include "settings/settings_common.h" +#include "settings/settings_global_ttl.h" #include "settings/settings_local_passcode.h" #include "settings/settings_premium.h" // Settings::ShowPremium. #include "settings/settings_privacy_controllers.h" @@ -31,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/core_settings.h" #include "ui/chat/chat_style.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/wrap/vertical_layout.h" @@ -459,9 +461,6 @@ void SetupCloudPassword( reloadOnActivation); session->api().cloudPassword().reload(); - - AddSkip(container); - AddDividerText(container, tr::lng_settings_cloud_password_start_about()); } void SetupSensitiveContent( @@ -671,6 +670,37 @@ void SetupSessionsList( }); } +void SetupGlobalTTLList( + not_null controller, + not_null container, + rpl::producer<> updateTrigger, + Fn showOther) { + const auto session = &controller->session(); + auto ttlLabel = rpl::combine( + session->api().selfDestruct().periodDefaultHistoryTTL(), + tr::lng_settings_ttl_after_off() + ) | rpl::map([](int ttl, const QString &none) { + return ttl ? Ui::FormatTTL(ttl) : none; + }); + const auto globalTTLButton = AddButtonWithLabel( + container, + tr::lng_settings_ttl_title(), + std::move(ttlLabel), + st::settingsButton, + { &st::settingsIconTTL, kIconPurple }); + globalTTLButton->addClickHandler([=] { + showOther(GlobalTTLId()); + }); + std::move( + updateTrigger + ) | rpl::start_with_next([=] { + session->api().selfDestruct().reload(); + }, container->lifetime()); + + AddSkip(container); + AddDividerText(container, tr::lng_settings_ttl_about()); +} + void SetupSecurity( not_null controller, not_null container, @@ -691,6 +721,11 @@ void SetupSecurity( showOther); SetupLocalPasscode(controller, container, showOther); SetupCloudPassword(controller, container, showOther); + SetupGlobalTTLList( + controller, + container, + rpl::duplicate(updateTrigger), + showOther); } } // namespace diff --git a/Telegram/SourceFiles/ui/text/format_values.cpp b/Telegram/SourceFiles/ui/text/format_values.cpp index 2aaa50e72..450eac08d 100644 --- a/Telegram/SourceFiles/ui/text/format_values.cpp +++ b/Telegram/SourceFiles/ui/text/format_values.cpp @@ -407,6 +407,30 @@ QString FormatTTL(float64 ttl) { : tr::lng_years({}, lt_count, std::round(ttl / (86400 * 365))); } +QString FormatTTLAfter(float64 ttl) { + return (ttl <= 3600 * 23) + ? tr::lng_settings_ttl_after_hours(tr::now, lt_count, int(ttl / 3600)) + : (ttl <= (86400) * 6) + ? tr::lng_settings_ttl_after_days( + tr::now, + lt_count, + int(ttl / (86400))) + : (ttl <= (86400 * 7) * 3) + ? tr::lng_settings_ttl_after_weeks( + tr::now, + lt_count, + int(ttl / (86400 * 7))) + : (ttl <= (86400 * 31) * 11) + ? tr::lng_settings_ttl_after_months( + tr::now, + lt_count, + int(ttl / (86400 * 31))) + : tr::lng_settings_ttl_after_years( + tr::now, + lt_count, + std::round(ttl / (86400 * 365))); +} + QString FormatTTLTiny(float64 ttl) { return (ttl <= 3600 * 9) ? tr::lng_hours_tiny(tr::now, lt_count, int(ttl / 3600)) diff --git a/Telegram/SourceFiles/ui/text/format_values.h b/Telegram/SourceFiles/ui/text/format_values.h index a92e0e441..a9251aabb 100644 --- a/Telegram/SourceFiles/ui/text/format_values.h +++ b/Telegram/SourceFiles/ui/text/format_values.h @@ -29,6 +29,7 @@ inline constexpr auto FileStatusSizeFailed = 0xFFFFFFF2LL; [[nodiscard]] QString FormatImageSizeText(const QSize &size); [[nodiscard]] QString FormatPhone(const QString &phone); [[nodiscard]] QString FormatTTL(float64 ttl); +[[nodiscard]] QString FormatTTLAfter(float64 ttl); [[nodiscard]] QString FormatTTLTiny(float64 ttl); [[nodiscard]] QString FormatMuteFor(float64 sec); [[nodiscard]] QString FormatMuteForTiny(float64 sec);