diff --git a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp new file mode 100644 index 000000000..9c3968bb3 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.cpp @@ -0,0 +1,227 @@ +/* +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 "info/boosts/giveaway/select_countries_box.h" + +#include "countries/countries_instance.h" +#include "lang/lang_keys.h" +#include "ui/emoji_config.h" +#include "ui/layers/generic_box.h" +#include "ui/painter.h" +#include "ui/rect.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/labels.h" +#include "ui/widgets/multi_select.h" +#include "ui/wrap/slide_wrap.h" +#include "styles/style_boxes.h" +#include "styles/style_giveaway.h" +#include "styles/style_settings.h" + +namespace Ui { +namespace { + +void AddSkip(not_null container) { + container->add(object_ptr( + container, + st::settingsSectionSkip)); +} + +[[nodiscard]] QImage CacheFlagEmoji(const QString &flag) { + const auto &st = st::giveawayGiftCodeCountrySelect.item; + auto roundPaintCache = QImage( + Size(st.height) * style::DevicePixelRatio(), + QImage::Format_ARGB32_Premultiplied); + roundPaintCache.setDevicePixelRatio(style::DevicePixelRatio()); + roundPaintCache.fill(Qt::transparent); + { + const auto size = st.height; + auto p = Painter(&roundPaintCache); + auto hq = PainterHighQualityEnabler(p); + const auto flagText = Ui::Text::String(st::defaultTextStyle, flag); + p.setPen(st.textBg); + p.setBrush(st.textBg); + p.drawEllipse(0, 0, size, size); + flagText.draw(p, { + .position = QPoint( + 0 + (size - flagText.maxWidth()) / 2, + 0 + (size - flagText.minHeight()) / 2), + .outerWidth = size, + .availableWidth = size, + }); + } + return roundPaintCache; +} + +} // namespace + +void SelectCountriesBox( + not_null box, + const std::vector &selected, + Fn)> doneCallback, + Fn checkErrorCallback) { + struct State final { + std::vector resultList; + }; + const auto state = box->lifetime().make_state(); + + const auto multiSelect = box->setPinnedToTopContent( + object_ptr( + box, + st::giveawayGiftCodeCountrySelect, + tr::lng_participant_filter())); + AddSkip(box->verticalLayout()); + const auto &buttonSt = st::giveawayGiftCodeCountryButton; + + struct Entry final { + Ui::SlideWrap *wrap = nullptr; + QStringList list; + QString iso2; + }; + + auto countries = Countries::Instance().list(); + ranges::sort(countries, []( + const Countries::Info &a, + const Countries::Info &b) { + return (a.name.compare(b.name, Qt::CaseInsensitive) < 0); + }); + auto buttons = std::vector(); + buttons.reserve(countries.size()); + for (const auto &country : countries) { + const auto flag = Countries::Instance().flagEmojiByISO2(country.iso2); + const auto asd = Ui::Emoji::Find(flag); + if (!Ui::Emoji::Find(flag)) { + continue; + } + const auto itemId = buttons.size(); + auto button = object_ptr( + box->verticalLayout(), + rpl::single(flag + ' ' + country.name), + buttonSt); + const auto radio = Ui::CreateChild(button.data()); + const auto radioView = std::make_shared( + st::defaultRadio, + false, + [=] { radio->update(); }); + + { + const auto radioSize = radioView->getSize(); + radio->resize(radioSize); + radio->paintRequest( + ) | rpl::start_with_next([=](const QRect &r) { + auto p = QPainter(radio); + radioView->paint(p, 0, 0, radioSize.width()); + }, radio->lifetime()); + const auto buttonHeight = buttonSt.height + + rect::m::sum::v(buttonSt.padding); + radio->moveToLeft( + st::giveawayRadioPosition.x(), + (buttonHeight - radioSize.height()) / 2); + } + + const auto roundPaintCache = CacheFlagEmoji(flag); + const auto paintCallback = [=](Painter &p, int x, int y, int, int) { + p.drawImage(x, y, roundPaintCache); + }; + const auto choose = [=](bool clicked) { + const auto value = !radioView->checked(); + if (value && checkErrorCallback(state->resultList.size())) { + return; + } + radioView->setChecked(value, anim::type::normal); + + if (value) { + state->resultList.push_back(country.iso2); + multiSelect->addItem( + itemId, + country.name, + st::activeButtonBg, + paintCallback, + clicked + ? Ui::MultiSelect::AddItemWay::Default + : Ui::MultiSelect::AddItemWay::SkipAnimation); + } else { + auto &list = state->resultList; + list.erase(ranges::remove(list, country.iso2), end(list)); + multiSelect->removeItem(itemId); + } + }; + button->setClickedCallback([=] { + choose(true); + }); + if (ranges::contains(selected, country.iso2)) { + choose(false); + } + + const auto wrap = box->verticalLayout()->add( + object_ptr>( + box, + std::move(button))); + wrap->toggle(true, anim::type::instant); + + { + auto list = QStringList{ + flag, + country.name, + country.alternativeName, + }; + buttons.push_back({ wrap, std::move(list), country.iso2 }); + } + } + + const auto noResults = box->addRow( + object_ptr>( + box, + object_ptr(box))); + { + noResults->toggle(false, anim::type::instant); + const auto container = noResults->entity(); + AddSkip(container); + AddSkip(container); + container->add( + object_ptr>( + container, + object_ptr( + container, + tr::lng_search_messages_none(), + st::membersAbout))); + AddSkip(container); + AddSkip(container); + } + + multiSelect->setQueryChangedCallback([=](const QString &query) { + auto wasAnyFound = false; + for (const auto &entry : buttons) { + const auto found = ranges::any_of(entry.list, [&]( + const QString &s) { + return s.startsWith(query, Qt::CaseInsensitive); + }); + entry.wrap->toggle(found, anim::type::instant); + wasAnyFound |= found; + } + noResults->toggle(!wasAnyFound, anim::type::instant); + }); + multiSelect->setItemRemovedCallback([=](uint64 itemId) { + auto &list = state->resultList; + auto &button = buttons[itemId]; + const auto it = ranges::find(list, button.iso2); + if (it != end(list)) { + list.erase(it); + button.wrap->entity()->clicked({}, Qt::LeftButton); + } + }); + + box->addButton(tr::lng_settings_save(), [=] { + doneCallback(state->resultList); + box->closeBox(); + }); + box->addButton(tr::lng_cancel(), [=] { + box->closeBox(); + }); +} + +} // namespace Ui diff --git a/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h new file mode 100644 index 000000000..0d0fb91a0 --- /dev/null +++ b/Telegram/SourceFiles/info/boosts/giveaway/select_countries_box.h @@ -0,0 +1,20 @@ +/* +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 + +namespace Ui { + +class GenericBox; + +void SelectCountriesBox( + not_null box, + const std::vector &selected, + Fn)> doneCallback, + Fn checkErrorCallback); + +} // namespace Ui diff --git a/Telegram/SourceFiles/statistics/statistics.style b/Telegram/SourceFiles/statistics/statistics.style index 9f7abbcdd..449c22293 100644 --- a/Telegram/SourceFiles/statistics/statistics.style +++ b/Telegram/SourceFiles/statistics/statistics.style @@ -160,3 +160,8 @@ giveawayUserpic: icon {{ "boosts/filled_gift", windowFgActive }}; giveawayUserpicSkip: 1px; giveawayUserpicGroup: icon {{ "limits/groups", windowFgActive }}; giveawayRadioPosition: point(21px, 16px); + +giveawayGiftCodeCountryButton: SettingsButton(reportReasonButton) { +} +giveawayGiftCodeCountrySelect: MultiSelect(defaultMultiSelect) { +} diff --git a/Telegram/cmake/td_ui.cmake b/Telegram/cmake/td_ui.cmake index 8205e5457..e0d6fa4f5 100644 --- a/Telegram/cmake/td_ui.cmake +++ b/Telegram/cmake/td_ui.cmake @@ -111,6 +111,8 @@ PRIVATE info/boosts/giveaway/giveaway_type_row.cpp info/boosts/giveaway/giveaway_type_row.h + info/boosts/giveaway/select_countries_box.cpp + info/boosts/giveaway/select_countries_box.h layout/abstract_layout_item.cpp layout/abstract_layout_item.h