tdesktop/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
John Preston f6ba59ed14 Improve group stickers choose process.
Allow to choose from featured if used has no his own sets.
Allow to choose group sticker set from group info box.
2017-11-22 13:31:02 +04:00

1329 lines
36 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop version of Telegram messaging app, see https://telegram.org
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
In addition, as a special exception, the copyright holders give permission
to link the code of portions of this program with the OpenSSL library.
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "boxes/peers/edit_peer_info_box.h"
#include <rpl/range.h>
#include <rpl/flatten_latest.h>
#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 "mtproto/sender.h"
#include "lang/lang_keys.h"
#include "mainwidget.h"
#include "messenger.h"
#include "apiwrap.h"
#include "application.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);
class Controller
: private MTP::Sender
, private base::enable_weak_from_this {
public:
Controller(
not_null<BoxContent*> box,
not_null<ChannelData*> channel);
object_ptr<Ui::VerticalLayout> createContent();
void setFocus();
private:
enum class Privacy {
Public,
Private,
};
enum class Invites {
Everyone,
OnlyAdmins,
};
enum class HistoryVisibility {
Visible,
Hidden,
};
enum class UsernameState {
Normal,
TooMany,
NotAvailable,
};
struct Controls {
Ui::InputField *title = nullptr;
Ui::InputArea *description = nullptr;
Ui::UserpicButton *photo = nullptr;
rpl::lifetime initialPhotoImageWaiting;
std::shared_ptr<Ui::RadioenumGroup<Privacy>> privacy;
Ui::SlideWrap<Ui::RpWidget> *usernameWrap = nullptr;
Ui::UsernameInput *username = nullptr;
base::unique_qptr<Ui::FlatLabel> usernameResult;
const style::FlatLabel *usernameResultStyle = nullptr;
Ui::SlideWrap<Ui::RpWidget> *createInviteLinkWrap = nullptr;
Ui::SlideWrap<Ui::RpWidget> *editInviteLinkWrap = nullptr;
Ui::FlatLabel *inviteLink = nullptr;
std::shared_ptr<Ui::RadioenumGroup<HistoryVisibility>> historyVisibility;
Ui::SlideWrap<Ui::RpWidget> *historyVisibilityWrap = nullptr;
std::shared_ptr<Ui::RadioenumGroup<Invites>> invites;
Ui::Checkbox *signatures = nullptr;
};
struct Saving {
base::optional<QString> username;
base::optional<QString> title;
base::optional<QString> description;
base::optional<bool> hiddenPreHistory;
base::optional<bool> signatures;
base::optional<bool> everyoneInvites;
};
base::lambda<QString()> computeTitle() const;
object_ptr<Ui::RpWidget> createPhotoAndTitleEdit();
object_ptr<Ui::RpWidget> createTitleEdit();
object_ptr<Ui::RpWidget> createPhotoEdit();
object_ptr<Ui::RpWidget> createDescriptionEdit();
object_ptr<Ui::RpWidget> createPrivaciesEdit();
object_ptr<Ui::RpWidget> createUsernameEdit();
object_ptr<Ui::RpWidget> createInviteLinkCreate();
object_ptr<Ui::RpWidget> createInviteLinkEdit();
object_ptr<Ui::RpWidget> createHistoryVisibilityEdit();
object_ptr<Ui::RpWidget> createSignaturesEdit();
object_ptr<Ui::RpWidget> createInvitesEdit();
object_ptr<Ui::RpWidget> createStickersEdit();
object_ptr<Ui::RpWidget> createDeleteButton();
void submitTitle();
void submitDescription();
void deleteWithConfirmation();
void privacyChanged(Privacy value);
void checkUsernameAvailability();
void askUsernameRevoke();
void usernameChanged();
void showUsernameError(rpl::producer<QString> &&error);
void showUsernameGood();
void showUsernameResult(
rpl::producer<QString> &&text,
not_null<const style::FlatLabel*> st);
bool canEditInviteLink() const;
bool inviteLinkShown() const;
void refreshEditInviteLink();
void refreshCreateInviteLink();
void refreshHistoryVisibility();
void createInviteLink();
void revokeInviteLink();
void exportInviteLink(const QString &confirmation);
base::optional<Saving> validate() const;
bool validateUsername(Saving &to) const;
bool validateTitle(Saving &to) const;
bool validateDescription(Saving &to) const;
bool validateHistoryVisibility(Saving &to) const;
bool validateInvites(Saving &to) const;
bool validateSignatures(Saving &to) const;
void save();
void saveUsername();
void saveTitle();
void saveDescription();
void saveHistoryVisibility();
void saveInvites();
void saveSignatures();
void savePhoto();
void pushSaveStage(base::lambda_once<void()> &&lambda);
void continueSave();
void cancelSave();
not_null<BoxContent*> _box;
not_null<ChannelData*> _channel;
bool _isGroup;
base::unique_qptr<Ui::VerticalLayout> _wrap;
Controls _controls;
base::Timer _checkUsernameTimer;
mtpRequestId _checkUsernameRequestId = 0;
UsernameState _usernameState = UsernameState::Normal;
rpl::event_stream<rpl::producer<QString>> _usernameResultTexts;
std::deque<base::lambda_once<void()>> _saveStagesQueue;
Saving _savingData;
};
Controller::Controller(
not_null<BoxContent*> box,
not_null<ChannelData*> channel)
: _box(box)
, _channel(channel)
, _isGroup(_channel->isMegagroup())
, _checkUsernameTimer([this] { checkUsernameAvailability(); }) {
_box->setTitle(computeTitle());
_box->addButton(langFactory(lng_settings_save), [this] {
save();
});
_box->addButton(langFactory(lng_cancel), [this] {
_box->closeBox();
});
}
base::lambda<QString()> Controller::computeTitle() const {
return langFactory(_isGroup
? lng_edit_group
: lng_edit_channel_title);
}
object_ptr<Ui::VerticalLayout> Controller::createContent() {
auto result = object_ptr<Ui::VerticalLayout>(_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(createInvitesEdit());
_wrap->add(createStickersEdit());
_wrap->add(createDeleteButton());
_wrap->resizeToWidth(st::boxWideWidth);
return result;
}
void Controller::setFocus() {
if (_controls.title) {
_controls.title->setFocusFast();
}
}
object_ptr<Ui::RpWidget> Controller::createPhotoAndTitleEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditInformation()) {
return nullptr;
}
auto result = object_ptr<Ui::RpWidget>(_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<Ui::RpWidget> Controller::createPhotoEdit() {
Expects(_wrap != nullptr);
using PhotoWrap = Ui::PaddingWrap<Ui::UserpicButton>;
auto photoWrap = object_ptr<PhotoWrap>(
_wrap,
object_ptr<Ui::UserpicButton>(
_wrap,
_box->controller(),
_channel,
Ui::UserpicButton::Role::ChangePhoto,
st::defaultUserpicButton),
st::editPeerPhotoMargins);
_controls.photo = photoWrap->entity();
return photoWrap;
}
object_ptr<Ui::RpWidget> Controller::createTitleEdit() {
Expects(_wrap != nullptr);
auto result = object_ptr<Ui::PaddingWrap<Ui::InputField>>(
_wrap,
object_ptr<Ui::InputField>(
_wrap,
st::defaultInputField,
langFactory(_isGroup
? lng_dlg_new_group_name
: lng_dlg_new_channel_name),
_channel->name),
st::editPeerTitleMargins);
QObject::connect(
result->entity(),
&Ui::InputField::submitted,
[this] { submitTitle(); });
_controls.title = result->entity();
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createDescriptionEdit() {
Expects(_wrap != nullptr);
auto result = object_ptr<Ui::PaddingWrap<Ui::InputArea>>(
_wrap,
object_ptr<Ui::InputArea>(
_wrap,
st::editPeerDescription,
langFactory(lng_create_group_description),
_channel->about()),
st::editPeerDescriptionMargins);
QObject::connect(
result->entity(),
&Ui::InputArea::submitted,
[this] { submitDescription(); });
_controls.description = result->entity();
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createPrivaciesEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditUsername()) {
return nullptr;
}
auto result = object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerPrivaciesMargins);
auto container = result->entity();
_controls.privacy = std::make_shared<Ui::RadioenumGroup<Privacy>>(
_channel->isPublic() ? Privacy::Public : Privacy::Private);
auto addButton = [&](
Privacy value,
LangKey groupTextKey,
LangKey channelTextKey,
LangKey groupAboutKey,
LangKey channelAboutKey) {
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerPrivacyTopSkip));
container->add(object_ptr<Ui::Radioenum<Privacy>>(
container,
_controls.privacy,
value,
lang(_isGroup ? groupTextKey : channelTextKey),
st::defaultBoxCheckbox));
container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(_isGroup ? groupAboutKey : channelAboutKey),
st::editPeerPrivacyLabel),
st::editPeerPrivacyLabelMargins));
container->add(object_ptr<Ui::FixedHeightWidget>(
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 (!_channel->isPublic()) {
checkUsernameAvailability();
}
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerUsernameMargins);
_controls.usernameWrap = result.data();
auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_create_group_link),
st::editPeerSectionLabel));
auto placeholder = container->add(object_ptr<Ui::RpWidget>(
container));
placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
_controls.username = Ui::AttachParentChild(
container,
object_ptr<Ui::UsernameInput>(
container,
st::setupChannelLink,
base::lambda<QString()>(),
_channel->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() < MinUsernameLength) {
return;
}
if (_checkUsernameRequestId) {
request(_checkUsernameRequestId).cancel();
}
_checkUsernameRequestId = request(MTPchannels_CheckUsername(
_channel->inputChannel,
MTP_string(checking)
)).done([=](const MTPBool &result) {
_checkUsernameRequestId = 0;
if (initial) {
return;
}
if (!mtpIsTrue(result) && checking != _channel->username) {
showUsernameError(
Lang::Viewer(lng_create_channel_link_occupied));
} else {
showUsernameGood();
}
}).fail([=](const RPCError &error) {
_checkUsernameRequestId = 0;
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 != _channel->username) {
showUsernameError(
Lang::Viewer(lng_create_channel_link_occupied));
}
}).send();
}
void Controller::askUsernameRevoke() {
_controls.privacy->setValue(Privacy::Private);
auto revokeCallback = base::lambda_guarded(this, [this] {
_usernameState = UsernameState::Normal;
_controls.privacy->setValue(Privacy::Public);
checkUsernameAvailability();
});
Ui::show(
Box<RevokePublicLinkBox>(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() < MinUsernameLength) {
showUsernameError(
Lang::Viewer(lng_create_channel_link_too_short));
} else {
_controls.usernameResult = nullptr;
_checkUsernameTimer.callOnce(kUsernameCheckTimeout);
}
}
void Controller::showUsernameError(rpl::producer<QString> &&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<QString> &&text,
not_null<const style::FlatLabel*> st) {
if (!_controls.usernameResult
|| _controls.usernameResultStyle != st) {
_controls.usernameResultStyle = st;
_controls.usernameResult = base::make_unique_q<Ui::FlatLabel>(
_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<QPointer<ConfirmBox>>();
auto callback = base::lambda_guarded(this, [=] {
if (auto strong = *boxPointer) {
strong->closeBox();
}
Auth().api().exportInviteLink(_channel);
});
auto box = Box<ConfirmBox>(
confirmation,
std::move(callback));
*boxPointer = Ui::show(std::move(box), LayerOption::KeepOther);
}
bool Controller::canEditInviteLink() const {
if (_channel->canEditUsername()) {
return true;
}
return (!_channel->isPublic() && _channel->canAddMembers());
}
bool Controller::inviteLinkShown() const {
return !_controls.privacy
|| (_controls.privacy->value() == Privacy::Private);
}
object_ptr<Ui::RpWidget> Controller::createInviteLinkEdit() {
Expects(_wrap != nullptr);
if (!canEditInviteLink()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInviteLinkMargins);
_controls.editInviteLinkWrap = result.data();
auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_profile_invite_link_section),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
_controls.inviteLink = container->add(object_ptr<Ui::FlatLabel>(
container,
st::editPeerInviteLink));
_controls.inviteLink->setSelectable(true);
_controls.inviteLink->setContextCopyText(QString());
_controls.inviteLink->setBreakEverywhere(true);
_controls.inviteLink->setClickHandlerHook([this](auto&&...) {
Application::clipboard()->setText(_channel->inviteLink());
Ui::Toast::Show(lang(lng_group_invite_copied));
return false;
});
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
container->add(object_ptr<Ui::LinkButton>(
container,
lang(lng_group_invite_create_new),
st::editPeerInviteLinkButton)
)->addClickHandler([this] { revokeInviteLink(); });
Notify::PeerUpdateValue(
_channel,
Notify::PeerUpdate::Flag::InviteLinkChanged)
| rpl::start_with_next([this] {
refreshEditInviteLink();
}, _controls.editInviteLinkWrap->lifetime());
return std::move(result);
}
void Controller::refreshEditInviteLink() {
auto link = _channel->inviteLink();
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<Ui::RpWidget> Controller::createInviteLinkCreate() {
Expects(_wrap != nullptr);
if (!canEditInviteLink()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInviteLinkMargins);
auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_profile_invite_link_section),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
container->add(object_ptr<Ui::LinkButton>(
_wrap,
lang(lng_group_invite_create),
st::editPeerInviteLinkButton)
)->addClickHandler([this] {
createInviteLink();
});
_controls.createInviteLinkWrap = result.data();
Notify::PeerUpdateValue(
_channel,
Notify::PeerUpdate::Flag::InviteLinkChanged)
| rpl::start_with_next([this] {
refreshCreateInviteLink();
}, _controls.createInviteLinkWrap->lifetime());
return std::move(result);
}
void Controller::refreshCreateInviteLink() {
auto link = _channel->inviteLink();
_controls.createInviteLinkWrap->toggle(
inviteLinkShown() && link.isEmpty(),
anim::type::instant);
}
object_ptr<Ui::RpWidget> Controller::createHistoryVisibilityEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditPreHistoryHidden()
|| !_channel->isMegagroup()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
_controls.historyVisibilityWrap = result.data();
auto container = result->entity();
_controls.historyVisibility
= std::make_shared<Ui::RadioenumGroup<HistoryVisibility>>(
_channel->hiddenPreHistory()
? HistoryVisibility::Hidden
: HistoryVisibility::Visible);
auto addButton = [&](
HistoryVisibility value,
LangKey groupTextKey,
LangKey groupAboutKey) {
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerPrivacyTopSkip + st::editPeerPrivacyBottomSkip));
container->add(object_ptr<Ui::Radioenum<HistoryVisibility>>(
container,
_controls.historyVisibility,
value,
lang(groupTextKey),
st::defaultBoxCheckbox));
container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
container,
object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(groupAboutKey),
st::editPeerPrivacyLabel),
st::editPeerPrivacyLabelMargins));
};
container->add(object_ptr<Ui::FlatLabel>(
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,
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<Ui::RpWidget> Controller::createSignaturesEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditSignatures()
|| _channel->isMegagroup()) {
return nullptr;
}
auto result = object_ptr<Ui::VerticalLayout>(_wrap);
auto container = result.data();
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::defaultBoxCheckbox.margin.top()));
_controls.signatures = container->add(
object_ptr<Ui::PaddingWrap<Ui::Checkbox>>(
container,
object_ptr<Ui::Checkbox>(
container,
lang(lng_edit_sign_messages),
_channel->addsSignature(),
st::defaultBoxCheckbox),
st::editPeerSignaturesMargins))->entity();
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::defaultBoxCheckbox.margin.bottom()));
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createInvitesEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditInvites()
|| !_channel->isMegagroup()) {
return nullptr;
}
auto result = object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInvitesMargins);
auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_edit_group_who_invites),
st::editPeerSectionLabel));
_controls.invites = std::make_shared<Ui::RadioenumGroup<Invites>>(
_channel->anyoneCanAddMembers()
? Invites::Everyone
: Invites::OnlyAdmins);
auto addButton = [&](
Invites value,
LangKey textKey) {
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInvitesTopSkip + st::editPeerInvitesSkip));
container->add(object_ptr<Ui::Radioenum<Invites>>(
container,
_controls.invites,
value,
lang(textKey),
st::defaultBoxCheckbox));
};
addButton(
Invites::Everyone,
lng_edit_group_invites_everybody);
addButton(
Invites::OnlyAdmins,
lng_edit_group_invites_only_admins);
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInvitesSkip));
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createStickersEdit() {
Expects(_wrap != nullptr);
if (!_channel->canEditStickers()) {
return nullptr;
}
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
_wrap,
object_ptr<Ui::VerticalLayout>(_wrap),
st::editPeerInviteLinkMargins);
auto container = result->entity();
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_group_stickers),
st::editPeerSectionLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
container->add(object_ptr<Ui::FlatLabel>(
container,
Lang::Viewer(lng_group_stickers_description),
st::editPeerPrivacyLabel));
container->add(object_ptr<Ui::FixedHeightWidget>(
container,
st::editPeerInviteLinkSkip));
container->add(object_ptr<Ui::LinkButton>(
_wrap,
lang(lng_group_stickers_add),
st::editPeerInviteLinkButton)
)->addClickHandler([this] {
Ui::show(Box<StickersBox>(_channel), LayerOption::KeepOther);
});
return std::move(result);
}
object_ptr<Ui::RpWidget> Controller::createDeleteButton() {
Expects(_wrap != nullptr);
if (!_channel->canDelete()) {
return nullptr;
}
auto text = lang(_isGroup
? lng_profile_delete_group
: lng_profile_delete_channel);
auto result = object_ptr<Ui::PaddingWrap<Ui::LinkButton>>(
_wrap,
object_ptr<Ui::LinkButton>(
_wrap,
text,
st::editPeerDeleteButton),
st::editPeerDeleteButtonMargins);
result->entity()->addClickHandler([this] {
deleteWithConfirmation();
});
return std::move(result);
}
void Controller::submitTitle() {
Expects(_controls.title != nullptr);
Expects(_controls.description != nullptr);
if (_controls.title->getLastText().isEmpty()) {
_controls.title->showError();
_box->scrollToWidget(_controls.title);
} else {
_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();
}
}
base::optional<Controller::Saving> Controller::validate() const {
auto result = Saving();
if (validateUsername(result)
&& validateTitle(result)
&& validateDescription(result)
&& validateHistoryVisibility(result)
&& validateInvites(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::validateInvites(Saving &to) const {
if (!_controls.invites) {
return true;
}
to.everyoneInvites
= (_controls.invites->value() == Invites::Everyone);
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] { saveInvites(); });
pushSaveStage([this] { saveSignatures(); });
pushSaveStage([this] { savePhoto(); });
continueSave();
}
}
void Controller::pushSaveStage(base::lambda_once<void()> &&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() {
if (!_savingData.username
|| *_savingData.username == _channel->username) {
return continueSave();
}
request(MTPchannels_UpdateUsername(
_channel->inputChannel,
MTP_string(*_savingData.username)
)).done([this](const MTPBool &result) {
_channel->setName(
TextUtilities::SingleLine(_channel->name),
*_savingData.username);
continueSave();
}).fail([this](const RPCError &error) {
auto type = error.type();
if (type == qstr("USERNAME_NOT_MODIFIED")) {
_channel->setName(
TextUtilities::SingleLine(_channel->name),
TextUtilities::SingleLine(*_savingData.username));
continueSave();
return;
}
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 == _channel->name) {
return continueSave();
}
request(MTPchannels_EditTitle(
_channel->inputChannel,
MTP_string(*_savingData.title)
)).done([this](const MTPUpdates &result) {
Auth().api().applyUpdates(result);
continueSave();
}).fail([this](const RPCError &error) {
auto type = error.type();
if (type == qstr("CHAT_NOT_MODIFIED")
|| type == qstr("CHAT_TITLE_NOT_MODIFIED")) {
_channel->setName(*_savingData.title, _channel->username);
continueSave();
return;
}
if (type == qstr("NO_CHAT_TITLE")) {
_controls.title->showError();
_box->scrollToWidget(_controls.title);
} else {
_controls.title->setFocus();
}
cancelSave();
}).send();
}
void Controller::saveDescription() {
if (!_savingData.description
|| *_savingData.description == _channel->about()) {
return continueSave();
}
auto successCallback = [this] {
_channel->setAbout(*_savingData.description);
continueSave();
};
request(MTPchannels_EditAbout(
_channel->inputChannel,
MTP_string(*_savingData.description)
)).done([=](const MTPBool &result) {
successCallback();
}).fail([=](const RPCError &error) {
auto type = error.type();
if (type == qstr("CHAT_ABOUT_NOT_MODIFIED")) {
successCallback();
return;
}
_controls.description->setFocus();
cancelSave();
}).send();
}
void Controller::saveHistoryVisibility() {
if (!_savingData.hiddenPreHistory
|| *_savingData.hiddenPreHistory == _channel->hiddenPreHistory()) {
return continueSave();
}
request(MTPchannels_TogglePreHistoryHidden(
_channel->inputChannel,
MTP_bool(*_savingData.hiddenPreHistory)
)).done([this](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();
Auth().api().applyUpdates(result);
continueSave();
}).fail([this](const RPCError &error) {
if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
continueSave();
} else {
cancelSave();
}
}).send();
}
void Controller::saveInvites() {
if (!_savingData.everyoneInvites
|| *_savingData.everyoneInvites == _channel->anyoneCanAddMembers()) {
return continueSave();
}
request(MTPchannels_ToggleInvites(
_channel->inputChannel,
MTP_bool(*_savingData.everyoneInvites)
)).done([this](const MTPUpdates &result) {
Auth().api().applyUpdates(result);
continueSave();
}).fail([this](const RPCError &error) {
if (error.type() == qstr("CHAT_NOT_MODIFIED")) {
continueSave();
} else {
cancelSave();
}
}).send();
}
void Controller::saveSignatures() {
if (!_savingData.signatures
|| *_savingData.signatures == _channel->addsSignature()) {
return continueSave();
}
request(MTPchannels_ToggleSignatures(
_channel->inputChannel,
MTP_bool(*_savingData.signatures)
)).done([this](const MTPUpdates &result) {
Auth().api().applyUpdates(result);
continueSave();
}).fail([this](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()) {
Messenger::Instance().uploadProfilePhoto(
std::move(image),
_channel->id);
}
_box->closeBox();
}
void Controller::deleteWithConfirmation() {
auto text = lang(_isGroup
? lng_sure_delete_group
: lng_sure_delete_channel);
auto deleteCallback = [channel = _channel] {
Ui::hideLayer();
Ui::showChatsList();
if (auto chat = channel->migrateFrom()) {
App::main()->deleteAndExit(chat);
}
MTP::send(
MTPchannels_DeleteChannel(channel->inputChannel),
App::main()->rpcDone(&MainWidget::sentUpdatesReceived),
App::main()->rpcFail(&MainWidget::deleteChannelFailed));
};
Ui::show(Box<ConfirmBox>(
text,
lang(lng_box_delete),
st::attentionBoxButton,
std::move(deleteCallback)), LayerOption::KeepOther);
}
} // namespace
EditPeerInfoBox::EditPeerInfoBox(
QWidget*,
not_null<ChannelData*> channel)
: _channel(channel) {
}
void EditPeerInfoBox::prepare() {
auto controller = std::make_unique<Controller>(this, _channel);
_focusRequests.events()
| rpl::start_with_next(
[c = controller.get()] { c->setFocus(); },
lifetime());
auto content = controller->createContent();
content->heightValue()
| rpl::start_with_next([this](int height) {
setDimensions(st::boxWideWidth, height);
}, content->lifetime());
setInnerWidget(object_ptr<Ui::IgnoreMargins>(
this,
std::move(content)));
Ui::AttachAsChild(this, std::move(controller));
}