| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- /*
- 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 "boxes/peers/choose_peer_box.h"
- #include "apiwrap.h" // ApiWrap::botCommonGroups / requestBotCommonGroups.
- #include "boxes/add_contact_box.h"
- #include "boxes/peer_list_controllers.h"
- #include "boxes/premium_limits_box.h"
- #include "data/data_chat.h"
- #include "data/data_channel.h"
- #include "data/data_peer.h"
- #include "data/data_user.h"
- #include "history/history.h"
- #include "history/history_item_reply_markup.h"
- #include "info/profile/info_profile_icon.h"
- #include "lang/lang_keys.h"
- #include "main/main_session.h" // Session::api().
- #include "ui/boxes/confirm_box.h"
- #include "ui/text/text_utilities.h"
- #include "ui/widgets/buttons.h"
- #include "ui/wrap/vertical_layout.h"
- #include "ui/vertical_list.h"
- #include "window/window_session_controller.h"
- #include "styles/style_boxes.h"
- #include "styles/style_chat_helpers.h"
- #include "styles/style_layers.h"
- namespace {
- class ChoosePeerBoxController final
- : public ChatsListBoxController
- , public base::has_weak_ptr {
- public:
- ChoosePeerBoxController(
- not_null<Window::SessionNavigation*> navigation,
- not_null<UserData*> bot,
- RequestPeerQuery query,
- Fn<void(std::vector<not_null<PeerData*>>)> callback);
- Main::Session &session() const override;
- void rowClicked(not_null<PeerListRow*> row) override;
- [[nodiscard]] rpl::producer<int> selectedCountValue() const;
- void submit();
- QString savedMessagesChatStatus() const override {
- return {};
- }
- private:
- void prepareViewHook() override;
- std::unique_ptr<Row> createRow(not_null<History*> history) override;
- QString emptyBoxText() const override;
- void prepareRestrictions();
- const not_null<Window::SessionNavigation*> _navigation;
- not_null<UserData*> _bot;
- RequestPeerQuery _query;
- base::flat_set<not_null<PeerData*>> _commonGroups;
- base::flat_set<not_null<PeerData*>> _selected;
- rpl::variable<int> _selectedCount;
- Fn<void(std::vector<not_null<PeerData*>>)> _callback;
- };
- using RightsMap = std::vector<std::pair<ChatAdminRight, tr::phrase<>>>;
- [[nodiscard]] RightsMap GroupRights() {
- using Flag = ChatAdminRight;
- return {
- { Flag::ChangeInfo, tr::lng_request_group_change_info },
- {
- Flag::DeleteMessages,
- tr::lng_request_group_delete_messages },
- { Flag::BanUsers, tr::lng_request_group_ban_users },
- { Flag::InviteByLinkOrAdd, tr::lng_request_group_invite },
- { Flag::PinMessages, tr::lng_request_group_pin_messages },
- { Flag::ManageTopics, tr::lng_request_group_manage_topics },
- {
- Flag::ManageCall,
- tr::lng_request_group_manage_video_chats },
- { Flag::Anonymous, tr::lng_request_group_anonymous },
- { Flag::AddAdmins, tr::lng_request_group_add_admins },
- };
- }
- [[nodiscard]] RightsMap BroadcastRights() {
- using Flag = ChatAdminRight;
- return {
- { Flag::ChangeInfo, tr::lng_request_channel_change_info },
- {
- Flag::PostMessages,
- tr::lng_request_channel_post_messages },
- {
- Flag::EditMessages,
- tr::lng_request_channel_edit_messages },
- {
- Flag::DeleteMessages,
- tr::lng_request_channel_delete_messages },
- {
- Flag::InviteByLinkOrAdd,
- tr::lng_request_channel_add_subscribers },
- {
- Flag::ManageCall,
- tr::lng_request_channel_manage_livestreams },
- { Flag::AddAdmins, tr::lng_request_channel_add_admins },
- };
- }
- [[nodiscard]] QString RightsText(
- ChatAdminRights rights,
- const RightsMap &phrases) {
- auto list = QStringList();
- for (const auto &[flag, phrase] : phrases) {
- if (rights & flag) {
- list.push_back(phrase(tr::now));
- }
- }
- const auto count = list.size();
- if (!count) {
- return QString();
- }
- const auto last = list.back();
- return (count > 1)
- ? tr::lng_request_peer_rights_and(
- tr::now,
- lt_rights,
- list.mid(0, count - 1).join(", "),
- lt_last,
- last)
- : last;
- }
- [[nodiscard]] QString GroupRightsText(ChatAdminRights rights) {
- return RightsText(rights, GroupRights());
- }
- [[nodiscard]] QString BroadcastRightsText(ChatAdminRights rights) {
- return RightsText(rights, BroadcastRights());
- }
- [[nodiscard]] QStringList RestrictionsList(RequestPeerQuery query) {
- using Type = RequestPeerQuery::Type;
- using Restriction = RequestPeerQuery::Restriction;
- auto result = QStringList();
- const auto addRestriction = [&](
- Restriction value,
- tr::phrase<> yes,
- tr::phrase<> no) {
- if (value == Restriction::Yes) {
- result.push_back(yes(tr::now));
- } else if (value == Restriction::No) {
- result.push_back(no(tr::now));
- }
- };
- const auto addRights = [&](const QString &rights) {
- if (!rights.isEmpty()) {
- result.push_back(
- tr::lng_request_peer_rights(tr::now, lt_rights, rights));
- }
- };
- switch (query.type) {
- case Type::User:
- if (query.userIsBot != Restriction::Yes) {
- addRestriction(
- query.userIsPremium,
- tr::lng_request_user_premium_yes,
- tr::lng_request_user_premium_no);
- }
- break;
- case Type::Group:
- addRestriction(
- query.hasUsername,
- tr::lng_request_group_public_yes,
- tr::lng_request_group_public_no);
- addRestriction(
- query.groupIsForum,
- tr::lng_request_group_topics_yes,
- tr::lng_request_group_topics_no);
- if (query.amCreator) {
- result.push_back(tr::lng_request_group_am_owner(tr::now));
- } else {
- addRights(GroupRightsText(query.myRights));
- }
- break;
- case Type::Broadcast:
- addRestriction(
- query.hasUsername,
- tr::lng_request_channel_public_yes,
- tr::lng_request_channel_public_no);
- if (query.amCreator) {
- result.push_back(tr::lng_request_channel_am_owner(tr::now));
- } else {
- addRights(BroadcastRightsText(query.myRights));
- }
- break;
- }
- return result;
- }
- object_ptr<Ui::BoxContent> MakeConfirmBox(
- not_null<UserData*> bot,
- not_null<PeerData*> peer,
- RequestPeerQuery query,
- Fn<void()> confirmed) {
- const auto name = peer->name();
- const auto botName = bot->name();
- auto text = tr::lng_request_peer_confirm(
- tr::now,
- lt_chat,
- Ui::Text::Bold(name),
- lt_bot,
- Ui::Text::Bold(botName),
- Ui::Text::WithEntities);
- if (!peer->isUser()) {
- const auto rights = peer->isBroadcast()
- ? BroadcastRightsText(query.botRights)
- : GroupRightsText(query.botRights);
- if (!rights.isEmpty()) {
- text.append('\n').append('\n').append(
- tr::lng_request_peer_confirm_rights(
- tr::now,
- lt_bot,
- Ui::Text::Bold(botName),
- lt_chat,
- Ui::Text::Bold(name),
- lt_rights,
- TextWithEntities{ rights },
- Ui::Text::WithEntities));
- } else if (!peer->isBroadcast() && query.isBotParticipant) {
- const auto common = bot->session().api().botCommonGroups(bot);
- if (!common || !ranges::contains(*common, peer)) {
- text.append('\n').append('\n').append(
- tr::lng_request_peer_confirm_add(
- tr::now,
- lt_bot,
- Ui::Text::Bold(botName),
- lt_chat,
- Ui::Text::Bold(name),
- Ui::Text::WithEntities));
- }
- }
- }
- return Ui::MakeConfirmBox({
- .text = std::move(text),
- .confirmed = [=](Fn<void()> close) { confirmed(); close(); },
- .confirmText = tr::lng_request_peer_confirm_send(tr::now),
- });
- }
- object_ptr<Ui::BoxContent> CreatePeerByQueryBox(
- not_null<Window::SessionNavigation*> navigation,
- not_null<UserData*> bot,
- RequestPeerQuery query,
- Fn<void(std::vector<not_null<PeerData*>>)> done) {
- const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
- auto callback = [=](not_null<PeerData*> peer) {
- done({ peer });
- if (const auto strong = weak->data()) {
- strong->closeBox();
- }
- };
- auto result = Box<GroupInfoBox>(
- navigation,
- bot,
- query,
- std::move(callback));
- *weak = result.data();
- return result;
- }
- [[nodiscard]] bool FilterPeerByQuery(
- not_null<PeerData*> peer,
- RequestPeerQuery query,
- const base::flat_set<not_null<PeerData*>> &commonGroups) {
- using Type = RequestPeerQuery::Type;
- using Restriction = RequestPeerQuery::Restriction;
- const auto checkRestriction = [](Restriction restriction, bool value) {
- return (restriction == Restriction::Any)
- || ((restriction == Restriction::Yes) == value);
- };
- const auto checkRights = [](
- ChatAdminRights wanted,
- bool creator,
- ChatAdminRights rights) {
- return creator || ((rights & wanted) == wanted);
- };
- switch (query.type) {
- case Type::User: {
- const auto user = peer->asUser();
- return user
- && !user->isInaccessible()
- && !user->isNotificationsUser()
- && checkRestriction(query.userIsBot, user->isBot())
- && checkRestriction(query.userIsPremium, user->isPremium());
- }
- case Type::Group: {
- const auto chat = peer->asChat();
- const auto megagroup = peer->asMegagroup();
- return (chat || megagroup)
- && (!query.amCreator
- || (chat ? chat->amCreator() : megagroup->amCreator()))
- && checkRestriction(query.groupIsForum, peer->isForum())
- && checkRestriction(
- query.hasUsername,
- megagroup && megagroup->hasUsername())
- && checkRights(
- query.myRights,
- chat ? chat->amCreator() : megagroup->amCreator(),
- chat ? chat->adminRights() : megagroup->adminRights())
- && (!query.isBotParticipant
- || query.myRights
- || commonGroups.contains(peer)
- || (chat
- ? chat->canAddMembers()
- : megagroup->canAddMembers()));
- }
- case Type::Broadcast: {
- const auto broadcast = peer->asBroadcast();
- return broadcast
- && (!query.amCreator || broadcast->amCreator())
- && checkRestriction(query.hasUsername, broadcast->hasUsername())
- && checkRights(
- query.myRights,
- broadcast->amCreator(),
- broadcast->adminRights());
- }
- }
- Unexpected("Type in FilterPeerByQuery.");
- }
- ChoosePeerBoxController::ChoosePeerBoxController(
- not_null<Window::SessionNavigation*> navigation,
- not_null<UserData*> bot,
- RequestPeerQuery query,
- Fn<void(std::vector<not_null<PeerData*>>)> callback)
- : ChatsListBoxController(&navigation->session())
- , _navigation(navigation)
- , _bot(bot)
- , _query(query)
- , _callback(std::move(callback)) {
- if (const auto list = _bot->session().api().botCommonGroups(_bot)) {
- _commonGroups = { begin(*list), end(*list) };
- }
- }
- Main::Session &ChoosePeerBoxController::session() const {
- return _navigation->session();
- }
- void ChoosePeerBoxController::prepareRestrictions() {
- auto above = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
- const auto raw = above.data();
- auto rows = RestrictionsList(_query);
- if (!rows.empty()) {
- Ui::AddSubsectionTitle(
- raw,
- tr::lng_request_peer_requirements(),
- { 0, st::membersMarginTop, 0, 0 });
- const auto skip = st::defaultSubsectionTitlePadding.left();
- auto separator = QString::fromUtf8("\n\xE2\x80\xA2 ");
- raw->add(
- object_ptr<Ui::FlatLabel>(
- raw,
- separator + rows.join(separator),
- st::requestPeerRestriction),
- { skip, 0, skip, st::membersMarginTop });
- Ui::AddDivider(raw);
- }
- const auto make = [&](tr::phrase<> text, const style::icon &st) {
- auto button = raw->add(
- object_ptr<Ui::SettingsButton>(
- raw,
- text(),
- st::inviteViaLinkButton),
- { 0, st::membersMarginTop, 0, 0 });
- const auto icon = Ui::CreateChild<Info::Profile::FloatingIcon>(
- button,
- st,
- QPoint());
- button->heightValue(
- ) | rpl::start_with_next([=](int height) {
- icon->moveToLeft(
- st::choosePeerCreateIconLeft,
- (height - st::inviteViaLinkIcon.height()) / 2);
- }, icon->lifetime());
- button->setClickedCallback([=] {
- _navigation->parentController()->show(
- CreatePeerByQueryBox(_navigation, _bot, _query, _callback));
- });
- button->events(
- ) | rpl::filter([=](not_null<QEvent*> e) {
- return (e->type() == QEvent::Enter);
- }) | rpl::start_with_next([=] {
- delegate()->peerListMouseLeftGeometry();
- }, button->lifetime());
- return button;
- };
- if (_query.type == RequestPeerQuery::Type::Group) {
- make(tr::lng_request_group_create, st::choosePeerGroupIcon);
- } else if (_query.type == RequestPeerQuery::Type::Broadcast) {
- make(tr::lng_request_channel_create, st::choosePeerChannelIcon);
- }
- if (raw->count() > 0) {
- delegate()->peerListSetAboveWidget(std::move(above));
- }
- }
- void ChoosePeerBoxController::prepareViewHook() {
- delegate()->peerListSetTitle([&] {
- using Type = RequestPeerQuery::Type;
- using Restriction = RequestPeerQuery::Restriction;
- switch (_query.type) {
- case Type::User: return (_query.userIsBot == Restriction::Yes)
- ? tr::lng_request_bot_title()
- : (_query.maxQuantity > 1)
- ? tr::lng_request_users_title()
- : tr::lng_request_user_title();
- case Type::Group: return tr::lng_request_group_title();
- case Type::Broadcast: return tr::lng_request_channel_title();
- }
- Unexpected("Type in RequestPeerQuery.");
- }());
- prepareRestrictions();
- }
- void ChoosePeerBoxController::rowClicked(not_null<PeerListRow*> row) {
- const auto limit = _query.maxQuantity;
- const auto multiselect = (limit > 1);
- const auto peer = row->peer();
- if (multiselect) {
- if (_selected.contains(peer) || _selected.size() < limit) {
- delegate()->peerListSetRowChecked(row, !row->checked());
- if (row->checked()) {
- _selected.emplace(peer);
- } else {
- _selected.remove(peer);
- }
- _selectedCount = int(_selected.size());
- }
- return;
- }
- const auto done = [callback = _callback, peer] {
- const auto onstack = callback;
- onstack({ peer });
- };
- if (const auto user = peer->asUser()) {
- done();
- } else {
- delegate()->peerListUiShow()->showBox(
- MakeConfirmBox(_bot, peer, _query, done));
- }
- }
- rpl::producer<int> ChoosePeerBoxController::selectedCountValue() const {
- return _selectedCount.value();
- }
- void ChoosePeerBoxController::submit() {
- const auto onstack = _callback;
- onstack(ranges::to_vector(_selected));
- }
- auto ChoosePeerBoxController::createRow(not_null<History*> history)
- -> std::unique_ptr<Row> {
- return FilterPeerByQuery(history->peer, _query, _commonGroups)
- ? std::make_unique<Row>(history)
- : nullptr;
- }
- QString ChoosePeerBoxController::emptyBoxText() const {
- using Type = RequestPeerQuery::Type;
- using Restriction = RequestPeerQuery::Restriction;
- const auto result = [](tr::phrase<> title, tr::phrase<> text) {
- return title(tr::now) + "\n\n" + text(tr::now);
- };
- switch (_query.type) {
- case Type::User: return (_query.userIsBot == Restriction::Yes)
- ? result(tr::lng_request_bot_no, tr::lng_request_bot_no_about)
- : result(tr::lng_request_user_no, tr::lng_request_user_no_about);
- case Type::Group:
- return result(
- tr::lng_request_group_no,
- tr::lng_request_group_no_about);
- case Type::Broadcast:
- return result(
- tr::lng_request_channel_no,
- tr::lng_request_channel_no_about);
- }
- Unexpected("Type in ChoosePeerBoxController::emptyBoxText.");
- }
- } // namespace
- void ShowChoosePeerBox(
- not_null<Window::SessionNavigation*> navigation,
- not_null<UserData*> bot,
- RequestPeerQuery query,
- Fn<void(std::vector<not_null<PeerData*>>)> chosen) {
- const auto needCommonGroups = query.isBotParticipant
- && (query.type == RequestPeerQuery::Type::Group)
- && !query.myRights;
- if (needCommonGroups && !bot->session().api().botCommonGroups(bot)) {
- const auto weak = base::make_weak(navigation);
- bot->session().api().requestBotCommonGroups(bot, [=] {
- if (const auto strong = weak.get()) {
- ShowChoosePeerBox(strong, bot, query, chosen);
- }
- });
- return;
- }
- const auto weak = std::make_shared<QPointer<Ui::BoxContent>>();
- auto callback = [=, done = std::move(chosen)](
- std::vector<not_null<PeerData*>> peers) {
- done(std::move(peers));
- if (const auto strong = weak->data()) {
- strong->closeBox();
- }
- };
- const auto limit = query.maxQuantity;
- auto controller = std::make_unique<ChoosePeerBoxController>(
- navigation,
- bot,
- query,
- std::move(callback));
- auto initBox = [=, ptr = controller.get()](not_null<PeerListBox*> box) {
- ptr->selectedCountValue() | rpl::start_with_next([=](int count) {
- box->clearButtons();
- if (limit > 1) {
- box->setAdditionalTitle(rpl::single(u"%1 / %2"_q.arg(count).arg(limit)));
- }
- if (count > 0) {
- box->addButton(tr::lng_intro_submit(), [=] {
- ptr->submit();
- if (*weak) {
- (*weak)->closeBox();
- }
- });
- }
- box->addButton(tr::lng_cancel(), [box] {
- box->closeBox();
- });
- }, box->lifetime());
- };
- *weak = navigation->parentController()->show(Box<PeerListBox>(
- std::move(controller),
- std::move(initBox)));
- }
|