diff --git a/Telegram/SourceFiles/api/api_bot.cpp b/Telegram/SourceFiles/api/api_bot.cpp index 9e6e182eb8..cc5a91ec53 100644 --- a/Telegram/SourceFiles/api/api_bot.cpp +++ b/Telegram/SourceFiles/api/api_bot.cpp @@ -424,7 +424,11 @@ void ActivateBotCommand(ClickHandlerContext context, int row, int column) { peer->session().api().applyUpdates(result); }).send(); }; - ShowChoosePeerBox(controller, chosen, query); + if (const auto bot = item->getMessageBot()) { + ShowChoosePeerBox(controller, bot, query, chosen); + } else { + LOG(("API Error: Bot not found for RequestPeer button.")); + } } break; case ButtonType::SwitchInlineSame: diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 66dac7ddd0..eec7e77f1a 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/add_contact_box.h" #include "lang/lang_keys.h" +#include "base/call_delayed.h" #include "base/random.h" #include "ui/boxes/confirm_box.h" #include "boxes/peer_list_controllers.h" @@ -20,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/core_settings.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "countries/countries_instance.h" // Countries::ExtractPhoneCode. +#include "history/history_item_reply_markup.h" #include "window/window_session_controller.h" #include "menu/menu_ttl.h" #include "ui/controls/userpic_button.h" @@ -72,7 +74,8 @@ void ChatCreateDone( not_null navigation, QImage image, TimeId ttlPeriod, - const MTPUpdates &updates) { + const MTPUpdates &updates, + Fn)> done) { navigation->session().api().applyUpdates(updates); const auto success = base::make_optional(&updates) @@ -106,7 +109,11 @@ void ChatCreateDone( if (ttlPeriod) { chat->setMessagesTTL(ttlPeriod); } - navigation->showPeerHistory(chat); + if (done) { + done(chat); + } else { + navigation->showPeerHistory(chat); + } }; if (!success) { LOG(("API Error: chat not found in updates " @@ -114,6 +121,25 @@ void ChatCreateDone( } } +void MustBePublicDestroy(not_null channel) { + const auto session = &channel->session(); + session->api().request(MTPchannels_DeleteChannel( + channel->inputChannel + )).done([=](const MTPUpdates &result) { + session->api().applyUpdates(result); + }).send(); +} + +void MustBePublicFailed( + not_null navigation, + not_null channel) { + const auto text = channel->isMegagroup() + ? "Can't create a public group :(" + : "Can't create a public channel :("; + Ui::Toast::Show(Window::Show(navigation).toastParent(), text); + MustBePublicDestroy(channel); +} + } // namespace TextWithEntities PeerFloodErrorText( @@ -456,7 +482,33 @@ GroupInfoBox::GroupInfoBox( , _api(&_navigation->session().mtp()) , _type(type) , _initialTitle(title) -, _channelDone(std::move(channelDone)) { +, _done([channelDone = std::move(channelDone)](not_null peer) { + if (const auto channel = peer->asChannel()) { + if (const auto onstack = channelDone) { + onstack(channel); + } + } +}) { +} + +GroupInfoBox::GroupInfoBox( + QWidget*, + not_null navigation, + not_null bot, + RequestPeerQuery query, + Fn)> done) +: _navigation(navigation) +, _api(&_navigation->session().mtp()) +, _type((query.type == RequestPeerQuery::Type::Broadcast) + ? Type::Channel + : (query.groupIsForum == RequestPeerQuery::Restriction::Yes) + ? Type::Forum + : (query.hasUsername == RequestPeerQuery::Restriction::Yes) + ? Type::Megagroup + : Type::Group) +, _mustBePublic(query.hasUsername == RequestPeerQuery::Restriction::Yes) +, _canAddBot(query.isBotParticipant ? bot.get() : nullptr) +, _done(std::move(done)) { } void GroupInfoBox::prepare() { @@ -466,7 +518,8 @@ void GroupInfoBox::prepare() { this, &_navigation->parentController()->window(), Ui::UserpicButton::Role::ChoosePhoto, - st::defaultUserpicButton); + st::defaultUserpicButton, + (_type == Type::Forum)); _photo->showCustomOnChosen(); _title.create( this, @@ -517,7 +570,7 @@ void GroupInfoBox::prepare() { connect(_title, &Ui::InputField::submitted, [=] { submitName(); }); addButton( - (_type != Type::Group + ((_type != Type::Group || _canAddBot) ? tr::lng_create_group_create() : tr::lng_create_group_next()), [=] { submit(); }); @@ -613,7 +666,7 @@ void GroupInfoBox::submitName() { } void GroupInfoBox::createGroup( - not_null selectUsersBox, + QPointer selectUsersBox, const QString &title, const std::vector> &users) { if (_creationRequestId) { @@ -643,16 +696,19 @@ void GroupInfoBox::createGroup( auto image = _photo->takeResultImage(); const auto period = _ttlPeriod; const auto navigation = _navigation; + const auto done = _done; getDelegate()->hideLayer(); // Destroys 'this'. - ChatCreateDone(navigation, std::move(image), period, result); + ChatCreateDone(navigation, std::move(image), period, result, done); }).fail([=](const MTP::Error &error) { const auto &type = error.type(); _creationRequestId = 0; const auto controller = _navigation->parentController(); if (type == u"NO_CHAT_TITLE"_q) { const auto weak = Ui::MakeWeak(this); - selectUsersBox->closeBox(); + if (const auto strong = selectUsersBox.data()) { + strong->closeBox(); + } if (weak) { _title->showError(); } @@ -693,14 +749,19 @@ void GroupInfoBox::submit() { } if (_type != Type::Group) { createChannel(title, description); + } else if (_canAddBot) { + createGroup(nullptr, title, { not_null(_canAddBot) }); } else { auto initBox = [title, weak = Ui::MakeWeak(this)]( not_null box) { auto create = [box, title, weak] { - if (weak) { + if (const auto strong = weak.data()) { auto rows = box->collectSelectedRows(); if (!rows.empty()) { - weak->createGroup(box, title, rows); + strong->createGroup( + box.get(), + title, + std::move(rows)); } } }; @@ -721,10 +782,13 @@ void GroupInfoBox::createChannel( const QString &description) { Expects(!_creationRequestId); - const auto flags = ((_type == Type::Megagroup) - ? MTPchannels_CreateChannel::Flag::f_megagroup - : MTPchannels_CreateChannel::Flag::f_broadcast) - | (_ttlPeriod + using Flag = MTPchannels_CreateChannel::Flag; + const auto flags = Flag() + | ((_type == Type::Megagroup || _type == Type::Forum) + ? Flag::f_megagroup + : Flag::f_broadcast) + | ((_type == Type::Forum) ? Flag::f_forum : Flag()) + | ((_type == Type::Megagroup && _ttlPeriod) ? MTPchannels_CreateChannel::Flag::f_ttl_period : MTPchannels_CreateChannel::Flags(0)); _creationRequestId = _api.request(MTPchannels_CreateChannel( @@ -818,14 +882,18 @@ void GroupInfoBox::checkInviteLink() { } void GroupInfoBox::channelReady() { - if (_channelDone) { - const auto callback = _channelDone; + if (_done && !_mustBePublic) { + const auto callback = _done; const auto argument = _createdChannel; closeBox(); callback(argument); } else { _navigation->parentController()->show( - Box(_navigation, _createdChannel), + Box( + _navigation, + _createdChannel, + _mustBePublic, + _done), Ui::LayerOption::CloseOther); } } @@ -853,11 +921,13 @@ SetupChannelBox::SetupChannelBox( QWidget*, not_null navigation, not_null channel, - bool existing) + bool mustBePublic, + Fn)> done) : _navigation(navigation) , _channel(channel) , _api(&_channel->session().mtp()) -, _existing(existing) +, _mustBePublic(mustBePublic) +, _done(std::move(done)) , _privacyGroup( std::make_shared>(Privacy::Public)) , _public( @@ -903,6 +973,10 @@ SetupChannelBox::SetupChannelBox( channel->username(), channel->session().createInternalLink(QString())) , _checkTimer([=] { check(); }) { + if (_mustBePublic) { + _public.destroy(); + _private.destroy(); + } } void SetupChannelBox::prepare() { @@ -923,9 +997,16 @@ void SetupChannelBox::prepare() { }).send(); addButton(tr::lng_settings_save(), [=] { save(); }); + + const auto cancel = [=] { + if (_mustBePublic) { + MustBePublicDestroy(_channel); + } + closeBox(); + }; addButton( - _existing ? tr::lng_cancel() : tr::lng_create_group_skip(), - [=] { closeBox(); }); + _mustBePublic ? tr::lng_cancel() : tr::lng_create_group_skip(), + cancel); connect(_link, &Ui::MaskedInputField::changed, [=] { handleChange(); }); _link->setVisible(_privacyGroup->value() == Privacy::Public); @@ -942,7 +1023,7 @@ void SetupChannelBox::prepare() { }, lifetime()); boxClosing() | rpl::start_with_next([=] { - if (!_existing) { + if (!_mustBePublic) { AddParticipantsBoxController::Start(_navigation, _channel); } }, lifetime()); @@ -961,12 +1042,16 @@ void SetupChannelBox::setInnerFocus() { void SetupChannelBox::updateMaxHeight() { auto newHeight = st::boxPadding.top() + st::newGroupPadding.top() - + _public->heightNoMargins() - + _aboutPublicHeight - + st::newGroupSkip - + _private->heightNoMargins() - + _aboutPrivate.countHeight(_aboutPublicWidth) - + st::newGroupSkip + + (_public + ? (_public->heightNoMargins() + + _aboutPublicHeight + + st::newGroupSkip) + : 0) + + (_private + ? (_private->heightNoMargins() + + _aboutPrivate.countHeight(_aboutPublicWidth) + + st::newGroupSkip) + : 0) + st::newGroupPadding.bottom(); if (!_channel->isMegagroup() || _privacyGroup->value() == Privacy::Public) { @@ -998,36 +1083,38 @@ void SetupChannelBox::paintEvent(QPaintEvent *e) { p.fillRect(e->rect(), st::boxBg); p.setPen(st::newGroupAboutFg); - const auto aboutPublic = QRect( - st::boxPadding.left() - + st::newGroupPadding.left() - + st::defaultRadio.diameter - + st::defaultBoxCheckbox.textPosition.x(), - _public->bottomNoMargins(), - _aboutPublicWidth, - _aboutPublicHeight); - _aboutPublic.drawLeft( - p, - aboutPublic.x(), - aboutPublic.y(), - aboutPublic.width(), - width()); - - const auto aboutPrivate = QRect( - st::boxPadding.left() - + st::newGroupPadding.left() - + st::defaultRadio.diameter - + st::defaultBoxCheckbox.textPosition.x(), - _private->bottomNoMargins(), - _aboutPublicWidth, - _aboutPublicHeight); - _aboutPrivate.drawLeft( - p, - aboutPrivate.x(), - aboutPrivate.y(), - aboutPrivate.width(), - width()); - + if (_public) { + const auto aboutPublic = QRect( + st::boxPadding.left() + + st::newGroupPadding.left() + + st::defaultRadio.diameter + + st::defaultBoxCheckbox.textPosition.x(), + _public->bottomNoMargins(), + _aboutPublicWidth, + _aboutPublicHeight); + _aboutPublic.drawLeft( + p, + aboutPublic.x(), + aboutPublic.y(), + aboutPublic.width(), + width()); + } + if (_private) { + const auto aboutPrivate = QRect( + st::boxPadding.left() + + st::newGroupPadding.left() + + st::defaultRadio.diameter + + st::defaultBoxCheckbox.textPosition.x(), + _private->bottomNoMargins(), + _aboutPublicWidth, + _aboutPublicHeight); + _aboutPrivate.drawLeft( + p, + aboutPrivate.x(), + aboutPrivate.y(), + aboutPrivate.width(), + width()); + } if (!_channel->isMegagroup() || !_link->isHidden()) { p.setPen(st::boxTextFg); p.setFont(st::newGroupLinkFont); @@ -1077,13 +1164,14 @@ void SetupChannelBox::resizeEvent(QResizeEvent *e) { BoxContent::resizeEvent(e); const auto left = st::boxPadding.left() + st::newGroupPadding.left(); - _public->moveToLeft( - left, - st::boxPadding.top() + st::newGroupPadding.top()); - _private->moveToLeft( - left, - _public->bottomNoMargins() + _aboutPublicHeight + st::newGroupSkip); - + if (_public && _private) { + _public->moveToLeft( + left, + st::boxPadding.top() + st::newGroupPadding.top()); + _private->moveToLeft( + left, + _public->bottomNoMargins() + _aboutPublicHeight + st::newGroupSkip); + } _link->resize( width() - st::boxPadding.left() @@ -1092,11 +1180,20 @@ void SetupChannelBox::resizeEvent(QResizeEvent *e) { _link->height()); _link->moveToLeft( st::boxPadding.left() + st::newGroupLinkPadding.left(), - _private->bottomNoMargins() - + _aboutPrivate.countHeight(_aboutPublicWidth) - + st::newGroupSkip + (st::boxPadding.top() + + st::newGroupPadding.top() + + (_public + ? (_public->heightNoMargins() + + _aboutPublicHeight + + st::newGroupSkip) + : 0) + + (_private + ? (_private->heightNoMargins() + + _aboutPrivate.countHeight(_aboutPublicWidth) + + st::newGroupSkip) + : 0) + st::newGroupPadding.bottom() - + st::newGroupLinkPadding.top()); + + st::newGroupLinkPadding.top())); _invitationLink = QRect( _link->x(), _link->y() + (_link->height() / 2) - st::boxTextFont->height, @@ -1144,10 +1241,15 @@ void SetupChannelBox::save() { _channel->inputChannel, MTP_string(_sentUsername) )).done([=] { + const auto done = _done; + const auto channel = _channel; _channel->setName( TextUtilities::SingleLine(_channel->name()), _sentUsername); - closeBox(); + closeBox(); // Deletes `this`. + if (done) { + done(channel); + } }).fail([=](const MTP::Error &error) { _saveRequestId = 0; updateFail(parseError(error.type())); @@ -1156,11 +1258,7 @@ void SetupChannelBox::save() { if (_saveRequestId) { return; } else if (_privacyGroup->value() == Privacy::Private) { - if (_existing) { - saveUsername(QString()); - } else { - closeBox(); - } + closeBox(); } else { const auto link = _link->text().trimmed(); if (link.isEmpty()) { @@ -1315,9 +1413,12 @@ void SetupChannelBox::updateFail(UsernameResult result) { void SetupChannelBox::checkFail(UsernameResult result) { if (result == UsernameResult::NA) { + if (_mustBePublic) { + mustBePublicFailed(); + } getDelegate()->hideLayer(); } else if (result == UsernameResult::ChatsTooMuch) { - if (_existing) { + if (_mustBePublic) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; @@ -1338,24 +1439,44 @@ void SetupChannelBox::checkFail(UsernameResult result) { void SetupChannelBox::showRevokePublicLinkBoxForEdit() { const auto channel = _channel; - const auto existing = _existing; + const auto mustBePublic = _mustBePublic; + const auto done = _done; const auto navigation = _navigation; + const auto revoked = std::make_shared(false); const auto callback = [=] { - Ui::show( - Box(navigation, channel, existing), + *revoked = true; + navigation->parentController()->show( + Box(navigation, channel, mustBePublic, done), Ui::LayerOption::KeepOther); }; - closeBox(); - Ui::show( + const auto revoker = navigation->parentController()->show( Box(PublicLinksLimitBox, navigation, callback), Ui::LayerOption::KeepOther); + const auto session = &navigation->session(); + revoker->boxClosing( + ) | rpl::start_with_next(crl::guard(session, [=] { + base::call_delayed(200, session, [=] { + if (*revoked) { + return; + } + MustBePublicDestroy(channel); + }); + }), revoker->lifetime()); + closeBox(); +} + +void SetupChannelBox::mustBePublicFailed() { + MustBePublicFailed(_navigation, _channel); } void SetupChannelBox::firstCheckFail(UsernameResult result) { if (result == UsernameResult::NA) { + if (_mustBePublic) { + mustBePublicFailed(); + } getDelegate()->hideLayer(); } else if (result == UsernameResult::ChatsTooMuch) { - if (_existing) { + if (_mustBePublic) { showRevokePublicLinkBoxForEdit(); } else { _tooMuchUsernames = true; diff --git a/Telegram/SourceFiles/boxes/add_contact_box.h b/Telegram/SourceFiles/boxes/add_contact_box.h index bc4bb2dcf2..dd181349a8 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.h +++ b/Telegram/SourceFiles/boxes/add_contact_box.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/sender.h" class PeerListBox; +struct RequestPeerQuery; namespace Window { class SessionNavigation; @@ -103,6 +104,12 @@ public: Type type, const QString &title = QString(), Fn)> channelDone = nullptr); + GroupInfoBox( + QWidget*, + not_null navigation, + not_null bot, + RequestPeerQuery query, + Fn)> done); protected: void prepare() override; @@ -113,7 +120,7 @@ protected: private: void createChannel(const QString &title, const QString &description); void createGroup( - not_null selectUsersBox, + QPointer selectUsersBox, const QString &title, const std::vector> &users); void submitName(); @@ -129,7 +136,9 @@ private: Type _type = Type::Group; QString _initialTitle; - Fn)> _channelDone; + bool _mustBePublic = false; + UserData *_canAddBot = nullptr; + Fn)> _done; object_ptr _photo = { nullptr }; object_ptr _title = { nullptr }; @@ -149,7 +158,8 @@ public: QWidget*, not_null navigation, not_null channel, - bool existing = false); + bool mustBePublic, + Fn)> done); void setInnerFocus() override; @@ -186,6 +196,7 @@ private: void updateFail(UsernameResult result); + void mustBePublicFailed(); void checkFail(UsernameResult result); void firstCheckFail(UsernameResult result); @@ -197,8 +208,9 @@ private: const not_null _channel; MTP::Sender _api; - bool _existing = false; bool _creatingInviteLink = false; + bool _mustBePublic = false; + Fn)> _done; std::shared_ptr> _privacyGroup; object_ptr> _public; diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp index b49d54f167..02de9b5e4a 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.cpp @@ -7,7 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "boxes/peers/choose_peer_box.h" +#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" @@ -30,9 +32,10 @@ class ChoosePeerBoxController final , public base::has_weak_ptr { public: ChoosePeerBoxController( - not_null session, - Fn)> callback, - RequestPeerQuery query); + not_null navigation, + not_null bot, + RequestPeerQuery query, + Fn)> callback); Main::Session &session() const override; void rowClicked(not_null row) override; @@ -48,9 +51,10 @@ private: void prepareRestrictions(); - const not_null _session; - Fn)> _callback; + const not_null _navigation; + not_null _bot; RequestPeerQuery _query; + Fn)> _callback; }; @@ -166,9 +170,24 @@ private: } object_ptr CreatePeerByQueryBox( - not_null session, - RequestPeerQuery query) { - return object_ptr(nullptr); + not_null navigation, + not_null bot, + RequestPeerQuery query, + Fn)> done) { + const auto weak = std::make_shared>(); + auto callback = [=](not_null peer) { + done(peer); + if (const auto strong = weak->data()) { + strong->closeBox(); + } + }; + auto result = Box( + navigation, + bot, + query, + std::move(callback)); + *weak = result.data(); + return result; } [[nodiscard]] bool FilterPeerByQuery( @@ -223,17 +242,19 @@ object_ptr CreatePeerByQueryBox( } ChoosePeerBoxController::ChoosePeerBoxController( - not_null session, - Fn)> callback, - RequestPeerQuery query) -: ChatsListBoxController(session) -, _session(session) -, _callback(std::move(callback)) -, _query(query) { + not_null navigation, + not_null bot, + RequestPeerQuery query, + Fn)> callback) +: ChatsListBoxController(&navigation->session()) +, _navigation(navigation) +, _bot(bot) +, _query(query) +, _callback(std::move(callback)) { } Main::Session &ChoosePeerBoxController::session() const { - return *_session; + return _navigation->session(); } void ChoosePeerBoxController::prepareRestrictions() { @@ -273,8 +294,8 @@ void ChoosePeerBoxController::prepareRestrictions() { }, icon->lifetime()); button->setClickedCallback([=] { - delegate()->peerListShowBox( - CreatePeerByQueryBox(&session(), _query)); + _navigation->parentController()->show( + CreatePeerByQueryBox(_navigation, _bot, _query, _callback)); }); button->events( @@ -351,8 +372,9 @@ QString ChoosePeerBoxController::emptyBoxText() const { QPointer ShowChoosePeerBox( not_null navigation, - Fn)> &&chosen, - RequestPeerQuery query) { + not_null bot, + RequestPeerQuery query, + Fn)> &&chosen) { const auto weak = std::make_shared>(); auto initBox = [=](not_null box) { box->addButton(tr::lng_cancel(), [box] { @@ -367,9 +389,10 @@ QPointer ShowChoosePeerBox( }; *weak = navigation->parentController()->show(Box( std::make_unique( - &navigation->session(), - std::move(callback), - query), + navigation, + bot, + query, + std::move(callback)), std::move(initBox)), Ui::LayerOption::KeepOther); return weak->data(); } diff --git a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h index 52bfc17a8f..7b1f866782 100644 --- a/Telegram/SourceFiles/boxes/peers/choose_peer_box.h +++ b/Telegram/SourceFiles/boxes/peers/choose_peer_box.h @@ -19,5 +19,6 @@ class SessionNavigation; QPointer ShowChoosePeerBox( not_null navigation, - Fn)> &&chosen, - RequestPeerQuery query); + not_null bot, + RequestPeerQuery query, + Fn)> &&chosen);