From 3ce201593c4e425aaa5d9130d606f69e39409769 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Thu, 9 Apr 2026 10:18:29 +0300 Subject: [PATCH] Added country-based poll restrictions with selectable allowed countries --- .../icons/poll/filled/filled_poll_country.svg | 13 +++ Telegram/Resources/langs/lang.strings | 6 ++ .../SourceFiles/boxes/create_poll_box.cpp | 87 +++++++++++++++++++ Telegram/SourceFiles/boxes/create_poll_box.h | 1 + Telegram/SourceFiles/boxes/polls.style | 1 + Telegram/SourceFiles/data/data_poll.cpp | 26 +++++- Telegram/SourceFiles/data/data_poll.h | 1 + 7 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 Telegram/Resources/icons/poll/filled/filled_poll_country.svg diff --git a/Telegram/Resources/icons/poll/filled/filled_poll_country.svg b/Telegram/Resources/icons/poll/filled/filled_poll_country.svg new file mode 100644 index 0000000000..bf77eb160f --- /dev/null +++ b/Telegram/Resources/icons/poll/filled/filled_poll_country.svg @@ -0,0 +1,13 @@ + + + Filled / filled_poll_country + + + + + + + + + + diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4af5e88359..e9b9151bd1 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -6997,6 +6997,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_polls_create_hide_results_about" = "If you switch this on, results will appear only after the poll closes."; "lng_polls_create_restrict_to_subscribers" = "Restrict to Subscribers"; "lng_polls_create_restrict_to_subscribers_about" = "Only subscribers who joined 24+ hours ago can vote."; +"lng_polls_create_limit_by_country" = "Limit by Country"; +"lng_polls_create_limit_by_country_about" = "Only users from selected countries can vote."; +"lng_polls_create_allowed_countries" = "Allowed Countries"; +"lng_polls_create_countries_count#one" = "{count} country"; +"lng_polls_create_countries_count#other" = "{count} countries"; +"lng_polls_create_choose_country" = "Please choose at least one country."; "lng_polls_create_duration_custom" = "Custom"; "lng_polls_create_deadline_title" = "Deadline"; "lng_polls_create_deadline_button" = "Set Deadline"; diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 5266267e82..5dc27e963e 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/event_filter.h" #include "base/random.h" #include "base/unique_qptr.h" +#include "countries/countries_instance.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/message_field.h" #include "chat_helpers/tabbed_panel.h" @@ -38,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/stickers/data_custom_emoji.h" #include "history/view/media/menu/history_view_poll_menu.h" #include "history/view/history_view_schedule_box.h" +#include "info/channel_statistics/boosts/giveaway/select_countries_box.h" #include "lang/lang_keys.h" #include "layout/layout_document_generic_preview.h" #include "main/main_app_config.h" @@ -1458,6 +1460,7 @@ object_ptr CreatePollBox::setupContent() { rpl::event_stream showWhoVotedForceOn; rpl::variable closePeriod = 0; rpl::variable closeDate = TimeId(0); + rpl::variable> countriesValue; std::shared_ptr descriptionMedia = std::make_shared(); std::shared_ptr solutionMedia @@ -2745,6 +2748,68 @@ object_ptr CreatePollBox::setupContent() { rpl::single(!!(_chosen & PollData::Flag::SubscribersOnly)), st::detailedSettingsButtonStyle).get() : nullptr; + const auto limitByCountry = isBroadcastChannel + ? AddPollToggleButton( + container, + tr::lng_polls_create_limit_by_country(), + tr::lng_polls_create_limit_by_country_about(), + { + .icon = &st::pollBoxFilledPollCountryIcon, + .background = &st::settingsIconBg6, + }, + rpl::single(false), + st::detailedSettingsButtonStyle).get() + : nullptr; + const auto countriesWrap = limitByCountry + ? container->add( + object_ptr>( + container, + object_ptr(container))) + : nullptr; + const auto countriesButton = [=] { + if (!countriesWrap) { + return (Ui::SettingsButton*)(nullptr); + } + const auto inner = countriesWrap->entity(); + return AddButtonWithLabel( + inner, + tr::lng_polls_create_allowed_countries(), + state->countriesValue.value( + ) | rpl::map([=](const std::vector &countries) { + if (countries.empty()) { + return QString(); + } + if (countries.size() == 1) { + return Countries::Instance().countryNameByISO2( + countries.front()); + } + return tr::lng_polls_create_countries_count( + tr::now, + lt_count, + countries.size()); + }), + st::settingsButtonNoIcon).get(); + }(); + if (countriesWrap) { + countriesWrap->toggleOn( + rpl::single(limitByCountry->toggled()) + | rpl::then(limitByCountry->toggledChanges())); + } + if (countriesButton) { + countriesButton->setClickedCallback([=] { + const auto done = [=](std::vector countries) { + state->countriesValue = std::move(countries); + }; + const auto checkError = [](int) { + return false; + }; + show->show(Box( + Ui::SelectCountriesBox, + state->countriesValue.current(), + done, + checkError)); + }); + } const auto solution = setupSolution( container, @@ -2879,6 +2944,10 @@ object_ptr CreatePollBox::setupContent() { && restrictToSubscribers->toggled()); const auto hideResultsEnabled = duration->toggled() && hideResults->toggled(); + result.countries = (limitByCountry + && limitByCountry->toggled()) + ? state->countriesValue.current() + : std::vector(); result.setFlags(Flag(0) | (publicVotes ? Flag::PublicVotes : Flag(0)) | (multiChoice ? Flag::MultiChoice : Flag(0)) @@ -2950,6 +3019,13 @@ object_ptr CreatePollBox::setupContent() { } else { state->error &= ~Error::Deadline; } + if (limitByCountry + && limitByCountry->toggled() + && state->countriesValue.current().empty()) { + state->error |= Error::Country; + } else { + state->error &= ~Error::Country; + } }; const auto showError = [show = uiShow()]( tr::phrase<> text) { @@ -3009,6 +3085,11 @@ object_ptr CreatePollBox::setupContent() { ShowMediaUploadingToast(); } else if (state->error & Error::Deadline) { showError(tr::lng_polls_create_deadline_expired); + } else if (state->error & Error::Country) { + showError(tr::lng_polls_create_choose_country); + if (countriesButton) { + scrollToWidget(countriesButton); + } } else if (!state->error) { auto result = collectResult(); result.options = sendOptions; @@ -3068,6 +3149,12 @@ object_ptr CreatePollBox::setupContent() { if (restrictToSubscribers) { restrictToSubscribers->finishAnimating(); } + if (limitByCountry) { + limitByCountry->finishAnimating(); + } + if (countriesWrap) { + countriesWrap->finishAnimating(); + } return result; } diff --git a/Telegram/SourceFiles/boxes/create_poll_box.h b/Telegram/SourceFiles/boxes/create_poll_box.h index 6940101697..1bd2518200 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.h +++ b/Telegram/SourceFiles/boxes/create_poll_box.h @@ -68,6 +68,7 @@ private: Solution = 0x10, Media = 0x20, Deadline = 0x40, + Country = 0x80, }; friend constexpr inline bool is_flag_type(Error) { return true; } using Errors = base::flags; diff --git a/Telegram/SourceFiles/boxes/polls.style b/Telegram/SourceFiles/boxes/polls.style index c1846047b2..f5f07922fa 100644 --- a/Telegram/SourceFiles/boxes/polls.style +++ b/Telegram/SourceFiles/boxes/polls.style @@ -137,6 +137,7 @@ pollBoxFilledPollRevoteIcon: icon{{ "poll/filled/filled_poll_revote", activeButt pollBoxFilledPollShuffleIcon: icon{{ "poll/filled/filled_poll_shuffle", activeButtonFg }}; pollBoxFilledPollMultipleIcon: icon{{ "poll/filled/filled_poll_multiple", activeButtonFg }}; pollBoxFilledPollSubscribersIcon: icon{{ "poll/filled/filled_poll_subscribers-20x20", activeButtonFg }}; +pollBoxFilledPollCountryIcon: icon{{ "poll/filled/filled_poll_country-20x20", activeButtonFg }}; pollAttachTextSkip: 28px; pollAttachProgressMargin: 4px; diff --git a/Telegram/SourceFiles/data/data_poll.cpp b/Telegram/SourceFiles/data/data_poll.cpp index 583377f3bf..23e046aa54 100644 --- a/Telegram/SourceFiles/data/data_poll.cpp +++ b/Telegram/SourceFiles/data/data_poll.cpp @@ -109,6 +109,13 @@ bool PollData::applyChanges(const MTPDpoll &poll) { | (poll.is_subscribers_only() ? Flag::SubscribersOnly : Flag(0)); const auto newCloseDate = poll.vclose_date().value_or_empty(); const auto newClosePeriod = poll.vclose_period().value_or_empty(); + auto newCountries = std::vector(); + if (const auto countries = poll.vcountries_iso2()) { + newCountries.reserve(countries->v.size()); + for (const auto &country : countries->v) { + newCountries.push_back(qs(country)); + } + } auto newAnswers = ranges::views::all( poll.vanswers().v ) | ranges::views::transform([&](const MTPPollAnswer &data) { @@ -146,6 +153,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) { const auto changed1 = (question != newQuestion) || (closeDate != newCloseDate) || (closePeriod != newClosePeriod) + || (countries != newCountries) || (_flags != newFlags); const auto changed2 = (answers != newAnswers); if (!changed1 && !changed2) { @@ -155,6 +163,7 @@ bool PollData::applyChanges(const MTPDpoll &poll) { question = newQuestion; closeDate = newCloseDate; closePeriod = newClosePeriod; + countries = std::move(newCountries); _flags = newFlags; } if (changed2) { @@ -410,6 +419,13 @@ QString PollData::debugString() const { if (!solution.text.isEmpty()) { result += u"Solution: "_q + solution.text + u'\n'; } + if (!countries.empty()) { + result += u"Countries: "_q + countries.front(); + for (auto i = 1, count = int(countries.size()); i != count; ++i) { + result += u", "_q + countries[i]; + } + result += u'\n'; + } return result; } @@ -528,6 +544,11 @@ MTPPoll PollDataToMTP(not_null poll, bool close) { poll->answers, ranges::back_inserter(answers), convert); + auto countries = QVector(); + countries.reserve(poll->countries.size()); + for (const auto &country : poll->countries) { + countries.push_back(MTP_string(country)); + } using Flag = MTPDpoll::Flag; const auto flags = ((poll->closed() || close) ? Flag::f_closed : Flag(0)) | (poll->multiChoice() ? Flag::f_multiple_choice : Flag(0)) @@ -541,7 +562,8 @@ MTPPoll PollDataToMTP(not_null poll, bool close) { : Flag(0)) | (poll->subscribersOnly() ? Flag::f_subscribers_only : Flag(0)) | (poll->closePeriod > 0 ? Flag::f_close_period : Flag(0)) - | (poll->closeDate > 0 ? Flag::f_close_date : Flag(0)); + | (poll->closeDate > 0 ? Flag::f_close_date : Flag(0)) + | (countries.isEmpty() ? Flag(0) : Flag::f_countries_iso2); return MTP_poll( MTP_long(poll->id), MTP_flags(flags), @@ -551,7 +573,7 @@ MTPPoll PollDataToMTP(not_null poll, bool close) { MTP_vector(answers), MTP_int(poll->closePeriod), MTP_int(poll->closeDate), - MTP_vector(), // countries_iso2 + MTP_vector(std::move(countries)), // countries_iso2 MTP_long(0)); } diff --git a/Telegram/SourceFiles/data/data_poll.h b/Telegram/SourceFiles/data/data_poll.h index cbc7d3713b..d3059639ce 100644 --- a/Telegram/SourceFiles/data/data_poll.h +++ b/Telegram/SourceFiles/data/data_poll.h @@ -114,6 +114,7 @@ struct PollData { TextWithEntities solution; PollMedia attachedMedia; PollMedia solutionMedia; + std::vector countries; TimeId closePeriod = 0; TimeId closeDate = 0; int totalVoters = 0;