/* 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/edit_peer_info_box.h" #include #include #include "info/profile/info_profile_button.h" #include "ui/wrap/vertical_layout.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/widgets/input_fields.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/labels.h" #include "ui/toast/toast.h" #include "ui/special_buttons.h" #include "boxes/confirm_box.h" #include "boxes/photo_crop_box.h" #include "boxes/add_contact_box.h" #include "boxes/stickers_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/peers/edit_participants_box.h" #include "data/data_peer.h" #include "data/data_chat.h" #include "data/data_channel.h" #include "chat_helpers/emoji_suggestions_widget.h" #include "mtproto/sender.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "core/application.h" #include "apiwrap.h" #include "auth_session.h" #include "observer_peer.h" #include "styles/style_boxes.h" #include "styles/style_info.h" namespace { constexpr auto kUsernameCheckTimeout = TimeMs(200); constexpr auto kMinUsernameLength = 5; constexpr auto kMaxGroupChannelTitle = 255; // See also add_contact_box. constexpr auto kMaxChannelDescription = 255; // See also add_contact_box. class Controller : public base::has_weak_ptr , private MTP::Sender { public: Controller( not_null box, not_null peer); object_ptr createContent(); void setFocus(); private: enum class Privacy { Public, Private, }; enum class HistoryVisibility { Visible, Hidden, }; enum class UsernameState { Normal, TooMany, NotAvailable, }; struct Controls { Ui::InputField *title = nullptr; Ui::InputField *description = nullptr; Ui::UserpicButton *photo = nullptr; rpl::lifetime initialPhotoImageWaiting; std::shared_ptr> privacy; Ui::SlideWrap *usernameWrap = nullptr; Ui::UsernameInput *username = nullptr; base::unique_qptr usernameResult; const style::FlatLabel *usernameResultStyle = nullptr; Ui::SlideWrap *createInviteLinkWrap = nullptr; Ui::SlideWrap *editInviteLinkWrap = nullptr; Ui::FlatLabel *inviteLink = nullptr; std::shared_ptr> historyVisibility; Ui::SlideWrap *historyVisibilityWrap = nullptr; Ui::Checkbox *signatures = nullptr; }; struct Saving { std::optional username; std::optional title; std::optional description; std::optional hiddenPreHistory; std::optional signatures; }; Fn computeTitle() const; object_ptr createPhotoAndTitleEdit(); object_ptr createTitleEdit(); object_ptr createPhotoEdit(); object_ptr createDescriptionEdit(); object_ptr createPrivaciesEdit(); object_ptr createUsernameEdit(); object_ptr createInviteLinkCreate(); object_ptr createInviteLinkEdit(); object_ptr createHistoryVisibilityEdit(); object_ptr createSignaturesEdit(); object_ptr createStickersEdit(); object_ptr createDeleteButton(); QString inviteLinkText() const; void observeInviteLink(); void submitTitle(); void submitDescription(); void deleteWithConfirmation(); void deleteChannel(); void privacyChanged(Privacy value); void checkUsernameAvailability(); void askUsernameRevoke(); void usernameChanged(); void showUsernameError(rpl::producer &&error); void showUsernameGood(); void showUsernameResult( rpl::producer &&text, not_null st); bool canEditInviteLink() const; bool inviteLinkShown() const; void refreshEditInviteLink(); void refreshCreateInviteLink(); void refreshHistoryVisibility(); void createInviteLink(); void revokeInviteLink(); void exportInviteLink(const QString &confirmation); std::optional validate() const; bool validateUsername(Saving &to) const; bool validateTitle(Saving &to) const; bool validateDescription(Saving &to) const; bool validateHistoryVisibility(Saving &to) const; bool validateSignatures(Saving &to) const; void save(); void saveUsername(); void saveTitle(); void saveDescription(); void saveHistoryVisibility(); void saveSignatures(); void savePhoto(); void pushSaveStage(FnMut &&lambda); void continueSave(); void cancelSave(); void subscribeToMigration(); void migrate(not_null channel); not_null _box; not_null _peer; bool _isGroup = false; base::unique_qptr _wrap; Controls _controls; base::Timer _checkUsernameTimer; mtpRequestId _checkUsernameRequestId = 0; UsernameState _usernameState = UsernameState::Normal; rpl::event_stream> _usernameResultTexts; std::deque> _saveStagesQueue; Saving _savingData; rpl::lifetime _lifetime; }; Controller::Controller( not_null box, not_null peer) : _box(box) , _peer(peer) , _isGroup(_peer->isChat() || _peer->isMegagroup()) , _checkUsernameTimer([=] { checkUsernameAvailability(); }) { _box->setTitle(computeTitle()); _box->addButton(langFactory(lng_settings_save), [this] { save(); }); _box->addButton(langFactory(lng_cancel), [this] { _box->closeBox(); }); subscribeToMigration(); _peer->updateFull(); } void Controller::subscribeToMigration() { SubscribeToMigration( _peer, _lifetime, [=](not_null channel) { migrate(channel); }); } void Controller::migrate(not_null channel) { _peer = channel; observeInviteLink(); _peer->updateFull(); } Fn Controller::computeTitle() const { return langFactory(_isGroup ? lng_edit_group : lng_edit_channel_title); } object_ptr Controller::createContent() { auto result = object_ptr(_box); _wrap.reset(result.data()); _controls = Controls(); _wrap->add(createPhotoAndTitleEdit()); _wrap->add(createDescriptionEdit()); _wrap->add(createPrivaciesEdit()); _wrap->add(createInviteLinkCreate()); _wrap->add(createInviteLinkEdit()); _wrap->add(createHistoryVisibilityEdit()); _wrap->add(createSignaturesEdit()); _wrap->add(createStickersEdit()); _wrap->add(createDeleteButton()); return result; } void Controller::setFocus() { if (_controls.title) { _controls.title->setFocusFast(); } } object_ptr Controller::createPhotoAndTitleEdit() { Expects(_wrap != nullptr); const auto canEdit = [&] { if (const auto channel = _peer->asChannel()) { return channel->canEditInformation(); } else if (const auto chat = _peer->asChat()) { return chat->canEditInformation(); } return false; }(); if (!canEdit) { return nullptr; } auto result = object_ptr(_wrap); auto container = result.data(); auto photoWrap = Ui::AttachParentChild( container, createPhotoEdit()); auto titleEdit = Ui::AttachParentChild( container, createTitleEdit()); photoWrap->heightValue( ) | rpl::start_with_next([container](int height) { container->resize(container->width(), height); }, photoWrap->lifetime()); container->widthValue( ) | rpl::start_with_next([titleEdit](int width) { auto left = st::editPeerPhotoMargins.left() + st::defaultUserpicButton.size.width(); titleEdit->resizeToWidth(width - left); titleEdit->moveToLeft(left, 0, width); }, titleEdit->lifetime()); return result; } object_ptr Controller::createPhotoEdit() { Expects(_wrap != nullptr); using PhotoWrap = Ui::PaddingWrap; auto photoWrap = object_ptr( _wrap, object_ptr( _wrap, _peer, Ui::UserpicButton::Role::ChangePhoto, st::defaultUserpicButton), st::editPeerPhotoMargins); _controls.photo = photoWrap->entity(); return photoWrap; } object_ptr Controller::createTitleEdit() { Expects(_wrap != nullptr); auto result = object_ptr>( _wrap, object_ptr( _wrap, st::defaultInputField, langFactory(_isGroup ? lng_dlg_new_group_name : lng_dlg_new_channel_name), _peer->name), st::editPeerTitleMargins); result->entity()->setMaxLength(kMaxGroupChannelTitle); result->entity()->setInstantReplaces(Ui::InstantReplaces::Default()); result->entity()->setInstantReplacesEnabled( Global::ReplaceEmojiValue()); Ui::Emoji::SuggestionsController::Init( _wrap->window(), result->entity()); QObject::connect( result->entity(), &Ui::InputField::submitted, [=] { submitTitle(); }); _controls.title = result->entity(); return std::move(result); } object_ptr Controller::createDescriptionEdit() { Expects(_wrap != nullptr); auto result = object_ptr>( _wrap, object_ptr( _wrap, st::editPeerDescription, Ui::InputField::Mode::MultiLine, langFactory(lng_create_group_description), _peer->about()), st::editPeerDescriptionMargins); result->entity()->setMaxLength(kMaxChannelDescription); result->entity()->setInstantReplaces(Ui::InstantReplaces::Default()); result->entity()->setInstantReplacesEnabled( Global::ReplaceEmojiValue()); Ui::Emoji::SuggestionsController::Init( _wrap->window(), result->entity()); QObject::connect( result->entity(), &Ui::InputField::submitted, [=] { submitDescription(); }); _controls.description = result->entity(); return std::move(result); } object_ptr Controller::createPrivaciesEdit() { Expects(_wrap != nullptr); const auto canEditUsername = [&] { if (const auto chat = _peer->asChat()) { return chat->canEditUsername(); } else if (const auto channel = _peer->asChannel()) { return channel->canEditUsername(); } Unexpected("Peer type in Controller::createPrivaciesEdit."); }(); if (!canEditUsername) { return nullptr; } auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerPrivaciesMargins); auto container = result->entity(); const auto isPublic = _peer->isChannel() && _peer->asChannel()->isPublic(); _controls.privacy = std::make_shared>( isPublic ? Privacy::Public : Privacy::Private); auto addButton = [&]( Privacy value, LangKey groupTextKey, LangKey channelTextKey, LangKey groupAboutKey, LangKey channelAboutKey) { container->add(object_ptr( container, st::editPeerPrivacyTopSkip)); container->add(object_ptr>( container, _controls.privacy, value, lang(_isGroup ? groupTextKey : channelTextKey), st::defaultBoxCheckbox)); container->add(object_ptr>( container, object_ptr( container, Lang::Viewer(_isGroup ? groupAboutKey : channelAboutKey), st::editPeerPrivacyLabel), st::editPeerPrivacyLabelMargins)); container->add(object_ptr( container, st::editPeerPrivacyBottomSkip)); }; addButton( Privacy::Public, lng_create_public_group_title, lng_create_public_channel_title, lng_create_public_group_about, lng_create_public_channel_about); addButton( Privacy::Private, lng_create_private_group_title, lng_create_private_channel_title, lng_create_private_group_about, lng_create_private_channel_about); container->add(createUsernameEdit()); _controls.privacy->setChangedCallback([this](Privacy value) { privacyChanged(value); }); if (!isPublic) { checkUsernameAvailability(); } return std::move(result); } object_ptr Controller::createUsernameEdit() { Expects(_wrap != nullptr); const auto channel = _peer->asChannel(); const auto username = channel ? channel->username : QString(); auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerUsernameMargins); _controls.usernameWrap = result.data(); auto container = result->entity(); container->add(object_ptr( container, Lang::Viewer(lng_create_group_link), st::editPeerSectionLabel)); auto placeholder = container->add(object_ptr( container)); placeholder->setAttribute(Qt::WA_TransparentForMouseEvents); _controls.username = Ui::AttachParentChild( container, object_ptr( container, st::setupChannelLink, Fn(), username, true)); _controls.username->heightValue( ) | rpl::start_with_next([placeholder](int height) { placeholder->resize(placeholder->width(), height); }, placeholder->lifetime()); placeholder->widthValue( ) | rpl::start_with_next([this](int width) { _controls.username->resize( width, _controls.username->height()); }, placeholder->lifetime()); _controls.username->move(placeholder->pos()); QObject::connect( _controls.username, &Ui::UsernameInput::changed, [this] { usernameChanged(); }); auto shown = (_controls.privacy->value() == Privacy::Public); result->toggle(shown, anim::type::instant); return std::move(result); } void Controller::privacyChanged(Privacy value) { auto toggleEditUsername = [&] { _controls.usernameWrap->toggle( (value == Privacy::Public), anim::type::instant); }; auto refreshVisibilities = [&] { // First we need to show everything, then hide anything. // Otherwise the scroll position could jump up undesirably. if (value == Privacy::Public) { toggleEditUsername(); } refreshCreateInviteLink(); refreshEditInviteLink(); refreshHistoryVisibility(); if (value == Privacy::Public) { _controls.usernameResult = nullptr; checkUsernameAvailability(); } else { toggleEditUsername(); } }; if (value == Privacy::Public) { if (_usernameState == UsernameState::TooMany) { askUsernameRevoke(); return; } else if (_usernameState == UsernameState::NotAvailable) { _controls.privacy->setValue(Privacy::Private); return; } refreshVisibilities(); _controls.username->setDisplayFocused(true); _controls.username->setFocus(); _box->scrollToWidget(_controls.username); } else { request(base::take(_checkUsernameRequestId)).cancel(); _checkUsernameTimer.cancel(); refreshVisibilities(); setFocus(); } } void Controller::checkUsernameAvailability() { if (!_controls.username) { return; } auto initial = (_controls.privacy->value() != Privacy::Public); auto checking = initial ? qsl(".bad.") : _controls.username->getLastText().trimmed(); if (checking.size() < kMinUsernameLength) { return; } if (_checkUsernameRequestId) { request(_checkUsernameRequestId).cancel(); } const auto channel = _peer->migrateToOrMe()->asChannel(); const auto username = channel ? channel->username : QString(); _checkUsernameRequestId = request(MTPchannels_CheckUsername( channel ? channel->inputChannel : MTP_inputChannelEmpty(), MTP_string(checking) )).done([=](const MTPBool &result) { _checkUsernameRequestId = 0; if (initial) { return; } if (!mtpIsTrue(result) && checking != username) { showUsernameError( Lang::Viewer(lng_create_channel_link_occupied)); } else { showUsernameGood(); } }).fail([=](const RPCError &error) { _checkUsernameRequestId = 0; const auto &type = error.type(); _usernameState = UsernameState::Normal; if (type == qstr("CHANNEL_PUBLIC_GROUP_NA")) { _usernameState = UsernameState::NotAvailable; _controls.privacy->setValue(Privacy::Private); } else if (type == qstr("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { _usernameState = UsernameState::TooMany; if (_controls.privacy->value() == Privacy::Public) { askUsernameRevoke(); } } else if (initial) { if (_controls.privacy->value() == Privacy::Public) { _controls.usernameResult = nullptr; _controls.username->setFocus(); _box->scrollToWidget(_controls.username); } } else if (type == qstr("USERNAME_INVALID")) { showUsernameError( Lang::Viewer(lng_create_channel_link_invalid)); } else if (type == qstr("USERNAME_OCCUPIED") && checking != username) { showUsernameError( Lang::Viewer(lng_create_channel_link_occupied)); } }).send(); } void Controller::askUsernameRevoke() { _controls.privacy->setValue(Privacy::Private); auto revokeCallback = crl::guard(this, [this] { _usernameState = UsernameState::Normal; _controls.privacy->setValue(Privacy::Public); checkUsernameAvailability(); }); Ui::show( Box(std::move(revokeCallback)), LayerOption::KeepOther); } void Controller::usernameChanged() { auto username = _controls.username->getLastText().trimmed(); if (username.isEmpty()) { _controls.usernameResult = nullptr; _checkUsernameTimer.cancel(); return; } auto bad = ranges::find_if(username, [](QChar ch) { return (ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && (ch != '_'); }) != username.end(); if (bad) { showUsernameError( Lang::Viewer(lng_create_channel_link_bad_symbols)); } else if (username.size() < kMinUsernameLength) { showUsernameError( Lang::Viewer(lng_create_channel_link_too_short)); } else { _controls.usernameResult = nullptr; _checkUsernameTimer.callOnce(kUsernameCheckTimeout); } } void Controller::showUsernameError(rpl::producer &&error) { showUsernameResult(std::move(error), &st::editPeerUsernameError); } void Controller::showUsernameGood() { showUsernameResult( Lang::Viewer(lng_create_channel_link_available), &st::editPeerUsernameGood); } void Controller::showUsernameResult( rpl::producer &&text, not_null st) { if (!_controls.usernameResult || _controls.usernameResultStyle != st) { _controls.usernameResultStyle = st; _controls.usernameResult = base::make_unique_q( _controls.usernameWrap, _usernameResultTexts.events() | rpl::flatten_latest(), *st); auto label = _controls.usernameResult.get(); label->show(); label->widthValue( ) | rpl::start_with_next([label] { label->moveToRight( st::editPeerUsernamePosition.x(), st::editPeerUsernamePosition.y()); }, label->lifetime()); } _usernameResultTexts.fire(std::move(text)); } void Controller::createInviteLink() { exportInviteLink(lang(_isGroup ? lng_group_invite_about : lng_group_invite_about_channel)); } void Controller::revokeInviteLink() { exportInviteLink(lang(lng_group_invite_about_new)); } void Controller::exportInviteLink(const QString &confirmation) { auto boxPointer = std::make_shared>(); auto callback = crl::guard(this, [=] { if (const auto strong = *boxPointer) { strong->closeBox(); } _peer->session().api().exportInviteLink(_peer->migrateToOrMe()); }); auto box = Box( confirmation, std::move(callback)); *boxPointer = Ui::show(std::move(box), LayerOption::KeepOther); } bool Controller::canEditInviteLink() const { if (const auto channel = _peer->asChannel()) { return channel->amCreator() || (channel->adminRights() & ChatAdminRight::f_invite_users); } else if (const auto chat = _peer->asChat()) { return chat->amCreator() || (chat->adminRights() & ChatAdminRight::f_invite_users); } return false; } bool Controller::inviteLinkShown() const { return !_controls.privacy || (_controls.privacy->value() == Privacy::Private); } QString Controller::inviteLinkText() const { if (const auto channel = _peer->asChannel()) { return channel->inviteLink(); } else if (const auto chat = _peer->asChat()) { return chat->inviteLink(); } return QString(); } void Controller::observeInviteLink() { if (!_controls.editInviteLinkWrap) { return; } Notify::PeerUpdateValue( _peer, Notify::PeerUpdate::Flag::InviteLinkChanged ) | rpl::start_with_next([=] { refreshCreateInviteLink(); refreshEditInviteLink(); }, _controls.editInviteLinkWrap->lifetime()); } object_ptr Controller::createInviteLinkEdit() { Expects(_wrap != nullptr); if (!canEditInviteLink()) { return nullptr; } auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerInviteLinkMargins); _controls.editInviteLinkWrap = result.data(); auto container = result->entity(); container->add(object_ptr( container, Lang::Viewer(lng_profile_invite_link_section), st::editPeerSectionLabel)); container->add(object_ptr( container, st::editPeerInviteLinkSkip)); _controls.inviteLink = container->add(object_ptr( container, st::editPeerInviteLink)); _controls.inviteLink->setSelectable(true); _controls.inviteLink->setContextCopyText(QString()); _controls.inviteLink->setBreakEverywhere(true); _controls.inviteLink->setClickHandlerFilter([=](auto&&...) { QApplication::clipboard()->setText(inviteLinkText()); Ui::Toast::Show(lang(lng_group_invite_copied)); return false; }); container->add(object_ptr( container, st::editPeerInviteLinkSkip)); container->add(object_ptr( container, lang(lng_group_invite_create_new), st::editPeerInviteLinkButton) )->addClickHandler([=] { revokeInviteLink(); }); observeInviteLink(); return std::move(result); } void Controller::refreshEditInviteLink() { auto link = inviteLinkText(); auto text = TextWithEntities(); if (!link.isEmpty()) { text.text = link; auto remove = qstr("https://"); if (text.text.startsWith(remove)) { text.text.remove(0, remove.size()); } text.entities.push_back(EntityInText( EntityInTextCustomUrl, 0, text.text.size(), link)); } _controls.inviteLink->setMarkedText(text); // Hack to expand FlatLabel width to naturalWidth again. _controls.editInviteLinkWrap->resizeToWidth(st::boxWideWidth); _controls.editInviteLinkWrap->toggle( inviteLinkShown() && !link.isEmpty(), anim::type::instant); } object_ptr Controller::createInviteLinkCreate() { Expects(_wrap != nullptr); if (!canEditInviteLink()) { return nullptr; } auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerInviteLinkMargins); auto container = result->entity(); container->add(object_ptr( container, Lang::Viewer(lng_profile_invite_link_section), st::editPeerSectionLabel)); container->add(object_ptr( container, st::editPeerInviteLinkSkip)); container->add(object_ptr( _wrap, lang(lng_group_invite_create), st::editPeerInviteLinkButton) )->addClickHandler([this] { createInviteLink(); }); _controls.createInviteLinkWrap = result.data(); observeInviteLink(); return std::move(result); } void Controller::refreshCreateInviteLink() { _controls.createInviteLinkWrap->toggle( inviteLinkShown() && inviteLinkText().isEmpty(), anim::type::instant); } object_ptr Controller::createHistoryVisibilityEdit() { Expects(_wrap != nullptr); const auto canEdit = [&] { if (const auto chat = _peer->asChat()) { return chat->canEditPreHistoryHidden(); } else if (const auto channel = _peer->asChannel()) { return channel->canEditPreHistoryHidden(); } Unexpected("User in Controller::createHistoryVisibilityEdit."); }(); if (!canEdit) { return nullptr; } const auto channel = _peer->asChannel(); auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerInvitesMargins); _controls.historyVisibilityWrap = result.data(); auto container = result->entity(); _controls.historyVisibility = std::make_shared>( (!channel || channel->hiddenPreHistory()) ? HistoryVisibility::Hidden : HistoryVisibility::Visible); auto addButton = [&]( HistoryVisibility value, LangKey groupTextKey, LangKey groupAboutKey) { container->add(object_ptr( container, st::editPeerPrivacyTopSkip + st::editPeerPrivacyBottomSkip)); container->add(object_ptr>( container, _controls.historyVisibility, value, lang(groupTextKey), st::defaultBoxCheckbox)); container->add(object_ptr>( container, object_ptr( container, Lang::Viewer(groupAboutKey), st::editPeerPrivacyLabel), st::editPeerPrivacyLabelMargins)); }; container->add(object_ptr( container, Lang::Viewer(lng_manage_history_visibility_title), st::editPeerSectionLabel)); addButton( HistoryVisibility::Visible, lng_manage_history_visibility_shown, lng_manage_history_visibility_shown_about); addButton( HistoryVisibility::Hidden, lng_manage_history_visibility_hidden, (_peer->isChat() ? lng_manage_history_visibility_hidden_legacy : lng_manage_history_visibility_hidden_about)); refreshHistoryVisibility(); return std::move(result); } void Controller::refreshHistoryVisibility() { if (!_controls.historyVisibilityWrap) { return; } auto historyVisibilityShown = !_controls.privacy || (_controls.privacy->value() == Privacy::Private); _controls.historyVisibilityWrap->toggle( historyVisibilityShown, anim::type::instant); } object_ptr Controller::createSignaturesEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditSignatures() || channel->isMegagroup()) { return nullptr; } auto result = object_ptr(_wrap); auto container = result.data(); container->add(object_ptr( container, st::defaultBoxCheckbox.margin.top())); _controls.signatures = container->add( object_ptr>( container, object_ptr( container, lang(lng_edit_sign_messages), channel->addsSignature(), st::defaultBoxCheckbox), st::editPeerSignaturesMargins))->entity(); container->add(object_ptr( container, st::defaultBoxCheckbox.margin.bottom())); return std::move(result); } object_ptr Controller::createStickersEdit() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canEditStickers()) { return nullptr; } auto result = object_ptr>( _wrap, object_ptr(_wrap), st::editPeerInviteLinkMargins); auto container = result->entity(); container->add(object_ptr( container, Lang::Viewer(lng_group_stickers), st::editPeerSectionLabel)); container->add(object_ptr( container, st::editPeerInviteLinkSkip)); container->add(object_ptr( container, Lang::Viewer(lng_group_stickers_description), st::editPeerPrivacyLabel)); container->add(object_ptr( container, st::editPeerInviteLinkSkip)); container->add(object_ptr( _wrap, lang(lng_group_stickers_add), st::editPeerInviteLinkButton) )->addClickHandler([=] { Ui::show(Box(channel), LayerOption::KeepOther); }); return std::move(result); } object_ptr Controller::createDeleteButton() { Expects(_wrap != nullptr); auto channel = _peer->asChannel(); if (!channel || !channel->canDelete()) { return nullptr; } auto text = lang(_isGroup ? lng_profile_delete_group : lng_profile_delete_channel); auto result = object_ptr>( _wrap, object_ptr( _wrap, text, st::editPeerDeleteButton), st::editPeerDeleteButtonMargins); result->entity()->addClickHandler([this] { deleteWithConfirmation(); }); return std::move(result); } void Controller::submitTitle() { Expects(_controls.title != nullptr); if (_controls.title->getLastText().isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); } else if (_controls.description) { _controls.description->setFocus(); _box->scrollToWidget(_controls.description); } } void Controller::submitDescription() { Expects(_controls.title != nullptr); Expects(_controls.description != nullptr); if (_controls.title->getLastText().isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); } else { save(); } } std::optional Controller::validate() const { auto result = Saving(); if (validateUsername(result) && validateTitle(result) && validateDescription(result) && validateHistoryVisibility(result) && validateSignatures(result)) { return result; } return {}; } bool Controller::validateUsername(Saving &to) const { if (!_controls.privacy) { return true; } else if (_controls.privacy->value() == Privacy::Private) { to.username = QString(); return true; } auto username = _controls.username->getLastText().trimmed(); if (username.isEmpty()) { _controls.username->showError(); _box->scrollToWidget(_controls.username); return false; } to.username = username; return true; } bool Controller::validateTitle(Saving &to) const { if (!_controls.title) { return true; } auto title = _controls.title->getLastText().trimmed(); if (title.isEmpty()) { _controls.title->showError(); _box->scrollToWidget(_controls.title); return false; } to.title = title; return true; } bool Controller::validateDescription(Saving &to) const { if (!_controls.description) { return true; } to.description = _controls.description->getLastText().trimmed(); return true; } bool Controller::validateHistoryVisibility(Saving &to) const { if (!_controls.historyVisibility || (_controls.privacy && _controls.privacy->value() == Privacy::Public)) { return true; } to.hiddenPreHistory = (_controls.historyVisibility->value() == HistoryVisibility::Hidden); return true; } bool Controller::validateSignatures(Saving &to) const { if (!_controls.signatures) { return true; } to.signatures = _controls.signatures->checked(); return true; } void Controller::save() { Expects(_wrap != nullptr); if (!_saveStagesQueue.empty()) { return; } if (auto saving = validate()) { _savingData = *saving; pushSaveStage([this] { saveUsername(); }); pushSaveStage([this] { saveTitle(); }); pushSaveStage([this] { saveDescription(); }); pushSaveStage([this] { saveHistoryVisibility(); }); pushSaveStage([this] { saveSignatures(); }); pushSaveStage([this] { savePhoto(); }); continueSave(); } } void Controller::pushSaveStage(FnMut &&lambda) { _saveStagesQueue.push_back(std::move(lambda)); } void Controller::continueSave() { if (!_saveStagesQueue.empty()) { auto next = std::move(_saveStagesQueue.front()); _saveStagesQueue.pop_front(); next(); } } void Controller::cancelSave() { _saveStagesQueue.clear(); } void Controller::saveUsername() { const auto channel = _peer->asChannel(); const auto username = (channel ? channel->username : QString()); if (!_savingData.username || *_savingData.username == username) { return continueSave(); } else if (!channel) { const auto saveForChannel = [=](not_null channel) { if (_peer->asChannel() == channel) { saveUsername(); } else { cancelSave(); } }; _peer->session().api().migrateChat( _peer->asChat(), crl::guard(this, saveForChannel)); return; } request(MTPchannels_UpdateUsername( channel->inputChannel, MTP_string(*_savingData.username) )).done([=](const MTPBool &result) { channel->setName( TextUtilities::SingleLine(channel->name), *_savingData.username); continueSave(); }).fail([=](const RPCError &error) { const auto &type = error.type(); if (type == qstr("USERNAME_NOT_MODIFIED")) { channel->setName( TextUtilities::SingleLine(channel->name), TextUtilities::SingleLine(*_savingData.username)); continueSave(); return; } const auto errorKey = [&] { if (type == qstr("USERNAME_INVALID")) { return lng_create_channel_link_invalid; } else if (type == qstr("USERNAME_OCCUPIED") || type == qstr("USERNAMES_UNAVAILABLE")) { return lng_create_channel_link_invalid; } return lng_create_channel_link_invalid; }(); _controls.username->showError(); _box->scrollToWidget(_controls.username); showUsernameError(Lang::Viewer(errorKey)); cancelSave(); }).send(); } void Controller::saveTitle() { if (!_savingData.title || *_savingData.title == _peer->name) { return continueSave(); } const auto onDone = [=](const MTPUpdates &result) { _peer->session().api().applyUpdates(result); continueSave(); }; const auto onFail = [=](const RPCError &error) { const auto &type = error.type(); if (type == qstr("CHAT_NOT_MODIFIED") || type == qstr("CHAT_TITLE_NOT_MODIFIED")) { if (const auto channel = _peer->asChannel()) { channel->setName(*_savingData.title, channel->username); } else if (const auto chat = _peer->asChat()) { chat->setName(*_savingData.title); } continueSave(); return; } _controls.title->showError(); if (type == qstr("NO_CHAT_TITLE")) { _box->scrollToWidget(_controls.title); } cancelSave(); }; if (const auto channel = _peer->asChannel()) { request(MTPchannels_EditTitle( channel->inputChannel, MTP_string(*_savingData.title) )).done(std::move(onDone) ).fail(std::move(onFail) ).send(); } else if (const auto chat = _peer->asChat()) { request(MTPmessages_EditChatTitle( chat->inputChat, MTP_string(*_savingData.title) )).done(std::move(onDone) ).fail(std::move(onFail) ).send(); } else { continueSave(); } } void Controller::saveDescription() { const auto channel = _peer->asChannel(); if (!_savingData.description || *_savingData.description == _peer->about()) { return continueSave(); } const auto successCallback = [=] { _peer->setAbout(*_savingData.description); continueSave(); }; request(MTPmessages_EditChatAbout( _peer->input, MTP_string(*_savingData.description) )).done([=](const MTPBool &result) { successCallback(); }).fail([=](const RPCError &error) { const auto &type = error.type(); if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) { successCallback(); return; } _controls.description->showError(); cancelSave(); }).send(); } void Controller::saveHistoryVisibility() { const auto channel = _peer->asChannel(); const auto hidden = channel ? channel->hiddenPreHistory() : true; if (!_savingData.hiddenPreHistory || *_savingData.hiddenPreHistory == hidden) { return continueSave(); } else if (!channel) { const auto saveForChannel = [=](not_null channel) { if (_peer->asChannel() == channel) { saveHistoryVisibility(); } else { cancelSave(); } }; _peer->session().api().migrateChat( _peer->asChat(), crl::guard(this, saveForChannel)); return; } request(MTPchannels_TogglePreHistoryHidden( channel->inputChannel, MTP_bool(*_savingData.hiddenPreHistory) )).done([=](const MTPUpdates &result) { // Update in the result doesn't contain the // channelFull:flags field which holds this value. // So after saving we need to update it manually. channel->updateFullForced(); channel->session().api().applyUpdates(result); continueSave(); }).fail([=](const RPCError &error) { if (error.type() == qstr("CHAT_NOT_MODIFIED")) { continueSave(); } else { cancelSave(); } }).send(); } void Controller::saveSignatures() { const auto channel = _peer->asChannel(); if (!_savingData.signatures || !channel || *_savingData.signatures == channel->addsSignature()) { return continueSave(); } request(MTPchannels_ToggleSignatures( channel->inputChannel, MTP_bool(*_savingData.signatures) )).done([=](const MTPUpdates &result) { channel->session().api().applyUpdates(result); continueSave(); }).fail([=](const RPCError &error) { if (error.type() == qstr("CHAT_NOT_MODIFIED")) { continueSave(); } else { cancelSave(); } }).send(); } void Controller::savePhoto() { auto image = _controls.photo ? _controls.photo->takeResultImage() : QImage(); if (!image.isNull()) { _peer->session().api().uploadPeerPhoto(_peer, std::move(image)); } _box->closeBox(); } void Controller::deleteWithConfirmation() { const auto channel = _peer->asChannel(); Assert(channel != nullptr); const auto text = lang(_isGroup ? lng_sure_delete_group : lng_sure_delete_channel); const auto deleteCallback = crl::guard(this, [=] { deleteChannel(); }); Ui::show( Box( text, lang(lng_box_delete), st::attentionBoxButton, deleteCallback), LayerOption::KeepOther); } void Controller::deleteChannel() { const auto channel = _peer->asChannel(); Assert(channel != nullptr); const auto chat = channel->migrateFrom(); Ui::hideLayer(); Ui::showChatsList(); if (chat) { App::main()->deleteAndExit(chat); } MTP::send( MTPchannels_DeleteChannel(channel->inputChannel), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::deleteChannelFailed)); } } // namespace EditPeerInfoBox::EditPeerInfoBox( QWidget*, not_null peer) : _peer(peer->migrateToOrMe()) { } void EditPeerInfoBox::prepare() { auto controller = Ui::CreateChild(this, this, _peer); _focusRequests.events( ) | rpl::start_with_next( [=] { controller->setFocus(); }, lifetime()); auto content = controller->createContent(); setDimensionsToContent(st::boxWideWidth, content); setInnerWidget(object_ptr( this, std::move(content))); }