// This is the source code of AyuGram for Desktop. // // We do not and cannot prevent the use of our code, // but be respectful and credit the original author. // // Copyright @Radolyn, 2023 #include "settings_ayu.h" #include "ayu/ayu_settings.h" #include "ayu/sync/ayu_sync_controller.h" #include "ayu/ui/boxes/edit_deleted_mark.h" #include "ayu/ui/boxes/edit_edited_mark.h" #include "apiwrap.h" #include "lang_auto.h" #include "mainwindow.h" #include "api/api_blocked_peers.h" #include "ayu/features/streamer_mode/streamer_mode.h" #include "boxes/connection_box.h" #include "core/application.h" #include "data/data_session.h" #include "lang/lang_instance.h" #include "main/main_session.h" #include "media/system_media_controls_manager.h" #include "platform/platform_specific.h" #include "settings/settings_common.h" #include "storage/localstorage.h" #include "styles/style_basic.h" #include "styles/style_boxes.h" #include "styles/style_info.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" #include "styles/style_widgets.h" #include "icon_picker.h" #include "ui/painter.h" #include "ui/boxes/confirm_box.h" #include "ui/boxes/single_choice_box.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/continuous_sliders.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" #include "window/window_session_controller.h" constexpr auto GhostModeOptionsCount = 5; class PainterHighQualityEnabler; const char kStreamerMode[] = "streamer-mode"; base::options::toggle StreamerMode({ .id = kStreamerMode, .name = "Show streamer mode toggles", .description = "Streamer mode completely hides AyuGram windows and notifications from capture apps.", .scope = base::options::windows, .restartRequired = true }); not_null AddInnerToggle( not_null container, const style::SettingsButton& st, std::vector> innerCheckViews, not_null*> wrap, rpl::producer buttonLabel, std::optional locked, Settings::IconDescriptor&& icon) { const auto button = container->add(object_ptr( container, nullptr, st)); if (icon) { AddButtonIcon(button, st, std::move(icon)); } const auto toggleButton = Ui::CreateChild( container.get(), nullptr, st); struct State final { State(const style::Toggle& st, Fn c) : checkView(st, false, c) { } Ui::ToggleView checkView; Ui::Animations::Simple animation; rpl::event_stream<> anyChanges; std::vector> innerChecks; }; const auto state = button->lifetime().make_state( st.toggle, [=] { toggleButton->update(); }); state->innerChecks = std::move(innerCheckViews); const auto countChecked = [=] { return ranges::count_if( state->innerChecks, [](const auto& v) { return v->checked(); }); }; for (const auto& innerCheck : state->innerChecks) { innerCheck->checkedChanges( ) | rpl::to_empty | start_to_stream( state->anyChanges, button->lifetime()); } const auto checkView = &state->checkView; { const auto separator = Ui::CreateChild(container.get()); separator->paintRequest( ) | start_with_next([=, bg = st.textBgOver] { auto p = QPainter(separator); p.fillRect(separator->rect(), bg); }, separator->lifetime()); const auto separatorHeight = 2 * st.toggle.border + st.toggle.diameter; button->geometryValue( ) | start_with_next([=](const QRect& r) { const auto w = st::rightsButtonToggleWidth; constexpr auto kLineWidth = 1; toggleButton->setGeometry( r.x() + r.width() - w, r.y(), w, r.height()); separator->setGeometry( toggleButton->x() - kLineWidth, r.y() + (r.height() - separatorHeight) / 2, kLineWidth, separatorHeight); }, toggleButton->lifetime()); const auto checkWidget = Ui::CreateChild(toggleButton); checkWidget->resize(checkView->getSize()); checkWidget->paintRequest( ) | start_with_next([=] { auto p = QPainter(checkWidget); checkView->paint(p, 0, 0, checkWidget->width()); }, checkWidget->lifetime()); toggleButton->sizeValue( ) | start_with_next([=](const QSize& s) { checkWidget->moveToRight( st.toggleSkip, (s.height() - checkWidget->height()) / 2); }, toggleButton->lifetime()); } state->anyChanges.events_starting_with( rpl::empty_value() ) | rpl::map(countChecked) | start_with_next([=](int count) { checkView->setChecked(count == GhostModeOptionsCount, anim::type::normal); }, toggleButton->lifetime()); checkView->setLocked(locked.has_value()); checkView->finishAnimating(); const auto totalInnerChecks = state->innerChecks.size(); const auto label = Ui::CreateChild( button, combine( std::move(buttonLabel), state->anyChanges.events_starting_with( rpl::empty_value() ) | rpl::map(countChecked) ) | rpl::map([=](const QString& t, int checked) { auto count = Ui::Text::Bold(" " + QString::number(checked) + '/' + QString::number(totalInnerChecks)); return TextWithEntities::Simple(t).append(std::move(count)); })); label->setAttribute(Qt::WA_TransparentForMouseEvents); const auto arrow = Ui::CreateChild(button); { const auto& icon = st::permissionsExpandIcon; arrow->resize(icon.size()); arrow->paintRequest( ) | start_with_next([=, &icon] { auto p = QPainter(arrow); const auto center = QPointF( icon.width() / 2., icon.height() / 2.); const auto progress = state->animation.value( wrap->toggled() ? 1. : 0.); auto hq = std::optional(); if (progress > 0.) { hq.emplace(p); p.translate(center); p.rotate(progress * 180.); p.translate(-center); } icon.paint(p, 0, 0, arrow->width()); }, arrow->lifetime()); } button->sizeValue( ) | start_with_next([=, &st](const QSize& s) { const auto labelLeft = st.padding.left(); const auto labelRight = s.width() - toggleButton->width(); label->resizeToWidth(labelRight - labelLeft - arrow->width()); label->moveToLeft( labelLeft, (s.height() - label->height()) / 2); arrow->moveToLeft( std::min( labelLeft + label->naturalWidth(), labelRight - arrow->width()), (s.height() - arrow->height()) / 2); }, button->lifetime()); wrap->toggledValue( ) | rpl::skip(1) | start_with_next([=](bool toggled) { state->animation.start( [=] { arrow->update(); }, toggled ? 0. : 1., toggled ? 1. : 0., st::slideWrapDuration); }, button->lifetime()); const auto handleLocked = [=] { if (locked.has_value()) { Ui::Toast::Show(container, *locked); return true; } return false; }; button->clicks( ) | start_with_next([=] { if (!handleLocked()) { wrap->toggle(!wrap->toggled(), anim::type::normal); } }, button->lifetime()); toggleButton->clicks( ) | start_with_next([=] { if (!handleLocked()) { const auto checked = !checkView->checked(); for (const auto& innerCheck : state->innerChecks) { innerCheck->setChecked(checked, anim::type::normal); } } }, toggleButton->lifetime()); return button; } namespace Settings { rpl::producer Ayu::title() { return tr::ayu_AyuPreferences(); } Ayu::Ayu( QWidget* parent, not_null controller) : Section(parent) { setupContent(controller); } void Ayu::AddPlatformOption( not_null window, not_null container, base::options::option& option, rpl::producer<> resetClicks) { auto& lifetime = container->lifetime(); const auto name = option.name().isEmpty() ? option.id() : option.name(); const auto toggles = lifetime.make_state>(); std::move( resetClicks ) | rpl::map_to( option.defaultValue() ) | start_to_stream(*toggles, lifetime); const auto button = AddButton( container, rpl::single(name), (option.relevant() ? st::settingsButtonNoIcon : st::settingsOptionDisabled) )->toggleOn(toggles->events_starting_with(option.value())); const auto restarter = (option.relevant() && option.restartRequired()) ? button->lifetime().make_state() : nullptr; if (restarter) { restarter->setCallback([=] { window->show(Ui::MakeConfirmBox({ .text = tr::lng_settings_need_restart(), .confirmed = [] { Core::Restart(); }, .confirmText = tr::lng_settings_restart_now(), .cancelText = tr::lng_settings_restart_later(), })); }); } button->toggledChanges( ) | start_with_next([=, &option](bool toggled) { if (!option.relevant() && toggled != option.defaultValue()) { toggles->fire_copy(option.defaultValue()); window->showToast( tr::lng_settings_experimental_irrelevant(tr::now)); return; } option.set(toggled); if (restarter) { restarter->callOnce(st::settingsButtonNoIcon.toggle.duration); } }, container->lifetime()); const auto& description = option.description(); if (!description.isEmpty()) { AddSkip(container); AddDividerText(container, rpl::single(description)); } } void Ayu::SetupGhostEssentials(not_null container) { auto settings = &AyuSettings::getInstance(); AddSubsectionTitle(container, tr::ayu_GhostEssentialsHeader()); const auto widget = object_ptr(this); widget->add( object_ptr( container, tr::ayu_GhostEssentialsHeader(), st::rightsHeaderLabel), st::rightsHeaderMargin); const auto addCheckbox = [&]( not_null verticalLayout, const QString& label, const bool isCheckedOrig) { const auto checkView = [&]() -> not_null { const auto checkbox = verticalLayout->add( object_ptr( verticalLayout, label, isCheckedOrig, st::settingsCheckbox), st::rightsButton.padding); const auto button = Ui::CreateChild( verticalLayout.get(), st::defaultRippleAnimation); button->stackUnder(checkbox); combine( verticalLayout->widthValue(), checkbox->geometryValue() ) | start_with_next([=](int w, const QRect& r) { button->setGeometry(0, r.y(), w, r.height()); }, button->lifetime()); checkbox->setAttribute(Qt::WA_TransparentForMouseEvents); const auto checkView = checkbox->checkView(); button->setClickedCallback([=] { checkView->setChecked( !checkView->checked(), anim::type::normal); }); return checkView; }(); checkView->checkedChanges( ) | start_with_next([=](bool checked) { }, verticalLayout->lifetime()); return checkView; }; struct NestedEntry { QString checkboxLabel; bool initial; std::function callback; }; std::vector checkboxes{ NestedEntry{ tr::ayu_DontReadMessages(tr::now), !settings->sendReadMessages, [=](bool enabled) { settings->set_sendReadMessages(!enabled); AyuSettings::save(); } }, NestedEntry{ tr::ayu_DontReadStories(tr::now), !settings->sendReadStories, [=](bool enabled) { settings->set_sendReadStories(!enabled); AyuSettings::save(); } }, NestedEntry{ tr::ayu_DontSendOnlinePackets(tr::now), !settings->sendOnlinePackets, [=](bool enabled) { settings->set_sendOnlinePackets(!enabled); AyuSettings::save(); } }, NestedEntry{ tr::ayu_DontSendUploadProgress(tr::now), !settings->sendUploadProgress, [=](bool enabled) { settings->set_sendUploadProgress(!enabled); AyuSettings::save(); } }, NestedEntry{ tr::ayu_SendOfflinePacketAfterOnline(tr::now), settings->sendOfflinePacketAfterOnline, [=](bool enabled) { settings->set_sendOfflinePacketAfterOnline(enabled); AyuSettings::save(); } }, }; auto wrap = object_ptr>( container, object_ptr(container)); const auto verticalLayout = wrap->entity(); auto innerChecks = std::vector>(); for (const auto& entry : checkboxes) { const auto c = addCheckbox(verticalLayout, entry.checkboxLabel, entry.initial); c->checkedValue( ) | start_with_next([=](bool enabled) { entry.callback(enabled); }, container->lifetime()); innerChecks.push_back(c); } const auto raw = wrap.data(); raw->hide(anim::type::instant); AddInnerToggle( container, st::rightsButton, innerChecks, raw, tr::ayu_GhostModeToggle(), std::nullopt, {}); container->add(std::move(wrap)); container->widthValue( ) | start_with_next([=](int w) { raw->resizeToWidth(w); }, raw->lifetime()); AddButton( container, tr::ayu_MarkReadAfterSend(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->markReadAfterSend) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->markReadAfterSend); }) | start_with_next([=](bool enabled) { settings->set_markReadAfterSend(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_UseScheduledMessages(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->useScheduledMessages) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->useScheduledMessages); }) | start_with_next([=](bool enabled) { settings->set_useScheduledMessages(enabled); AyuSettings::save(); }, container->lifetime()); } void Ayu::SetupSpyEssentials(not_null container) { auto settings = &AyuSettings::getInstance(); AddSubsectionTitle(container, tr::ayu_SpyEssentialsHeader()); AddButton( container, tr::ayu_SaveDeletedMessages(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->saveDeletedMessages) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->saveDeletedMessages); }) | start_with_next([=](bool enabled) { settings->set_keepDeletedMessages(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_SaveMessagesHistory(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->saveMessagesHistory) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->saveMessagesHistory); }) | start_with_next([=](bool enabled) { settings->set_keepMessagesHistory(enabled); AyuSettings::save(); }, container->lifetime()); } void Ayu::SetupQoLToggles(not_null container) { auto settings = &AyuSettings::getInstance(); AddSubsectionTitle(container, tr::ayu_QoLTogglesHeader()); AddButton( container, tr::ayu_DisableAds(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->disableAds) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->disableAds); }) | start_with_next([=](bool enabled) { settings->set_disableAds(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_DisableStories(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->disableStories) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->disableStories); }) | start_with_next([=](bool enabled) { settings->set_disableStories(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_LocalPremium(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->localPremium) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->localPremium); }) | start_with_next([=](bool enabled) { settings->set_localPremium(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_CopyUsernameAsLink(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->copyUsernameAsLink) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->copyUsernameAsLink); }) | start_with_next([=](bool enabled) { settings->set_copyUsernameAsLink(enabled); AyuSettings::save(); }, container->lifetime()); } void Ayu::SetupAppIcon(not_null container) { container->add( object_ptr(container), st::settingsSubsectionTitlePadding); } void Ayu::SetupCustomization(not_null container, not_null controller) { auto settings = &AyuSettings::getInstance(); AddSubsectionTitle(container, tr::ayu_CustomizationHeader()); SetupAppIcon(container); auto btn = AddButtonWithLabel( container, tr::ayu_DeletedMarkText(), AyuSettings::get_deletedMarkReactive(), st::settingsButtonNoIcon ); btn->addClickHandler([=]() { auto box = Box(); Ui::show(std::move(box)); }); auto btn2 = AddButtonWithLabel( container, tr::ayu_EditedMarkText(), AyuSettings::get_editedMarkReactive(), st::settingsButtonNoIcon ); btn2->addClickHandler([=]() { auto box = Box(); Ui::show(std::move(box)); }); SetupRecentStickersLimitSlider(container); SetupShowPeerId(container, controller); AddButton( container, tr::ayu_HideAllChats(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->hideAllChatsFolder) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->hideAllChatsFolder); }) | start_with_next([=](bool enabled) { settings->set_hideAllChatsFolder(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_ShowGhostToggleInDrawer(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->showGhostToggleInDrawer) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->showGhostToggleInDrawer); }) | start_with_next([=](bool enabled) { settings->set_showGhostToggleInDrawer(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_SettingsShowMessageSeconds(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->showMessageSeconds) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->showMessageSeconds); }) | start_with_next([=](bool enabled) { settings->set_showMessageSeconds(enabled); AyuSettings::save(); }, container->lifetime()); } void Ayu::SetupShowPeerId(not_null container, not_null controller) { auto settings = &AyuSettings::getInstance(); const auto options = std::vector{ QString(tr::ayu_SettingsShowID_Hide(tr::now)), QString("Telegram API"), QString("Bot API") }; auto currentVal = AyuSettings::get_showPeerIdReactive() | rpl::map([=](int val) { return options[val]; }); const auto button = AddButtonWithLabel( container, tr::ayu_SettingsShowID(), currentVal, st::settingsButtonNoIcon); button->addClickHandler([=] { controller->show(Box([=](not_null box) { const auto save = [=](int index) { settings->set_showPeerId(index); AyuSettings::save(); }; SingleChoiceBox(box, { .title = tr::ayu_SettingsShowID(), .options = options, .initialSelection = settings->showPeerId, .callback = save, }); })); }); } void Ayu::SetupRecentStickersLimitSlider(not_null container) { auto settings = &AyuSettings::getInstance(); container->add( CreateButton( container, tr::ayu_SettingsRecentStickersCount(), st::settingsButtonNoIcon) ); auto recentStickersLimitSlider = MakeSliderWithLabel( container, st::settingsScale, st::settingsScaleLabel, st::normalFont->spacew * 2, st::settingsScaleLabel.style.font->width("300%")); container->add( std::move(recentStickersLimitSlider.widget), st::settingsScalePadding); const auto slider = recentStickersLimitSlider.slider; const auto label = recentStickersLimitSlider.label; const auto updateLabel = [=](int amount) { label->setText(QString::number(amount)); }; updateLabel(settings->recentStickersCount); slider->setPseudoDiscrete( 100 + 1, // thx tg [=](int amount) { return amount; }, settings->recentStickersCount, [=](int amount) { updateLabel(amount); }, [=](int amount) { updateLabel(amount); settings->set_recentStickersCount(amount); AyuSettings::save(); }); } void Ayu::SetupAyuSync(not_null container) { AddSubsectionTitle(container, tr::ayu_AyuSyncHeader()); auto text = AyuSync::isAgentDownloaded() ? tr::ayu_AyuSyncOpenPreferences() : tr::ayu_AyuSyncDownloadAgent(); AddButton( container, text, st::settingsButtonNoIcon )->addClickHandler([=] { auto controller = &AyuSync::getInstance(); controller->initializeAgent(); }); } void Ayu::SetupSendConfirmations(not_null container) { auto settings = &AyuSettings::getInstance(); AddSubsectionTitle(container, tr::ayu_ConfirmationsTitle()); AddButton( container, tr::ayu_StickerConfirmation(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->stickerConfirmation) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->stickerConfirmation); }) | start_with_next([=](bool enabled) { settings->set_stickerConfirmation(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_GIFConfirmation(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->gifConfirmation) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->gifConfirmation); }) | start_with_next([=](bool enabled) { settings->set_gifConfirmation(enabled); AyuSettings::save(); }, container->lifetime()); AddButton( container, tr::ayu_VoiceConfirmation(), st::settingsButtonNoIcon )->toggleOn( rpl::single(settings->voiceConfirmation) )->toggledValue( ) | rpl::filter([=](bool enabled) { return (enabled != settings->voiceConfirmation); }) | start_with_next([=](bool enabled) { settings->set_voiceConfirmation(enabled); AyuSettings::save(); }, container->lifetime()); } void Ayu::SetupExperimental(not_null container, not_null controller) { AddSubsectionTitle(container, tr::lng_settings_experimental()); AddPlatformOption(controller, container, StreamerMode, rpl::producer<>()); } void Ayu::SetupAyuGramSettings(not_null container, not_null controller) { AddSkip(container); SetupGhostEssentials(container); AddSkip(container); AddDivider(container); AddSkip(container); SetupSpyEssentials(container); AddSkip(container); AddDivider(container); AddSkip(container); SetupQoLToggles(container); AddSkip(container); AddDivider(container); AddSkip(container); SetupCustomization(container, controller); AddSkip(container); AddDividerText(container, tr::ayu_SettingsCustomizationHint()); // todo: compilation flag if constexpr (false) { AddSkip(container); SetupAyuSync(container); AddSkip(container); AddDivider(container); } AddSkip(container); SetupSendConfirmations(container); AddSkip(container); AddDivider(container); SetupExperimental(container, controller); AddDividerText(container, tr::ayu_SettingsWatermark()); } void Ayu::setupContent(not_null controller) { const auto content = Ui::CreateChild(this); SetupAyuGramSettings(content, controller); ResizeFitChild(this, content); } } // namespace Settings