795 lines
22 KiB
C++
795 lines
22 KiB
C++
/*
|
|
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_type_box.h"
|
|
|
|
#include "main/main_session.h"
|
|
#include "boxes/add_contact_box.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "boxes/premium_limits_box.h"
|
|
#include "boxes/peer_list_controllers.h"
|
|
#include "boxes/peers/edit_participants_box.h"
|
|
#include "boxes/peers/edit_peer_common.h"
|
|
#include "boxes/peers/edit_peer_info_box.h" // CreateButton.
|
|
#include "boxes/peers/edit_peer_invite_link.h"
|
|
#include "boxes/peers/edit_peer_invite_links.h"
|
|
#include "boxes/peers/edit_peer_usernames_list.h"
|
|
#include "boxes/username_box.h"
|
|
#include "chat_helpers/emoji_suggestions_widget.h"
|
|
#include "data/data_channel.h"
|
|
#include "data/data_chat.h"
|
|
#include "data/data_peer.h"
|
|
#include "data/data_session.h"
|
|
#include "data/data_changes.h"
|
|
#include "info/profile/info_profile_values.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "mtproto/sender.h"
|
|
#include "ui/rp_widget.h"
|
|
#include "ui/special_buttons.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/checkbox.h"
|
|
#include "ui/widgets/input_fields.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/box_content_divider.h"
|
|
#include "ui/wrap/padding_wrap.h"
|
|
#include "ui/wrap/slide_wrap.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "ui/widgets/fields/special_fields.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "settings/settings_common.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_info.h"
|
|
#include "styles/style_settings.h"
|
|
|
|
namespace {
|
|
|
|
class Controller : public base::has_weak_ptr {
|
|
public:
|
|
Controller(
|
|
Window::SessionNavigation *navigation,
|
|
std::shared_ptr<Ui::BoxShow> show,
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<PeerData*> peer,
|
|
bool useLocationPhrases,
|
|
std::optional<EditPeerTypeData> dataSavedValue);
|
|
|
|
void createContent();
|
|
[[nodiscard]] QString getUsernameInput() const;
|
|
[[nodiscard]] std::vector<QString> usernamesOrder() const;
|
|
void setFocusUsername();
|
|
|
|
[[nodiscard]] rpl::producer<QString> getTitle() const {
|
|
return !_dataSavedValue
|
|
? tr::lng_create_invite_link_title()
|
|
: _isGroup
|
|
? tr::lng_manage_peer_group_type()
|
|
: tr::lng_manage_peer_channel_type();
|
|
}
|
|
|
|
[[nodiscard]] bool goodUsername() const {
|
|
return _goodUsername;
|
|
}
|
|
|
|
[[nodiscard]] Privacy getPrivacy() const {
|
|
return _controls.privacy->value();
|
|
}
|
|
|
|
[[nodiscard]] bool noForwards() const {
|
|
return _controls.noForwards->toggled();
|
|
}
|
|
[[nodiscard]] bool joinToWrite() const {
|
|
return _controls.joinToWrite && _controls.joinToWrite->toggled();
|
|
}
|
|
[[nodiscard]] bool requestToJoin() const {
|
|
return _controls.requestToJoin && _controls.requestToJoin->toggled();
|
|
}
|
|
|
|
[[nodiscard]] rpl::producer<int> scrollToRequests() const {
|
|
return _scrollToRequests.events();
|
|
}
|
|
|
|
void showError(rpl::producer<QString> text) {
|
|
_controls.usernameInput->showError();
|
|
showUsernameError(std::move(text));
|
|
}
|
|
|
|
private:
|
|
struct Controls {
|
|
std::shared_ptr<Ui::RadioenumGroup<Privacy>> privacy;
|
|
Ui::SlideWrap<Ui::RpWidget> *usernameWrap = nullptr;
|
|
Ui::UsernameInput *usernameInput = nullptr;
|
|
UsernamesList *usernamesList = nullptr;
|
|
base::unique_qptr<Ui::FlatLabel> usernameCheckResult;
|
|
|
|
Ui::SlideWrap<> *inviteLinkWrap = nullptr;
|
|
Ui::FlatLabel *inviteLink = nullptr;
|
|
|
|
Ui::SlideWrap<Ui::VerticalLayout> *whoSendWrap = nullptr;
|
|
Ui::SettingsButton *noForwards = nullptr;
|
|
Ui::SettingsButton *joinToWrite = nullptr;
|
|
Ui::SettingsButton *requestToJoin = nullptr;
|
|
};
|
|
|
|
Controls _controls;
|
|
|
|
object_ptr<Ui::RpWidget> createUsernameEdit();
|
|
object_ptr<Ui::RpWidget> createInviteLinkBlock();
|
|
|
|
void privacyChanged(Privacy value);
|
|
|
|
void checkUsernameAvailability();
|
|
void askUsernameRevoke();
|
|
void usernameChanged();
|
|
void showUsernameError(rpl::producer<QString> &&error);
|
|
void showUsernameGood();
|
|
void showUsernamePending();
|
|
void showUsernameEmpty();
|
|
|
|
void fillPrivaciesButtons(
|
|
not_null<Ui::VerticalLayout*> parent,
|
|
std::optional<Privacy> savedValue);
|
|
void addRoundButton(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Privacy value,
|
|
const QString &text,
|
|
rpl::producer<QString> about);
|
|
|
|
Window::SessionNavigation *_navigation = nullptr;
|
|
std::shared_ptr<Ui::BoxShow> _show;
|
|
|
|
not_null<PeerData*> _peer;
|
|
bool _linkOnly = false;
|
|
|
|
MTP::Sender _api;
|
|
std::optional<EditPeerTypeData> _dataSavedValue;
|
|
|
|
bool _useLocationPhrases = false;
|
|
bool _isGroup = false;
|
|
bool _goodUsername = false;
|
|
|
|
base::unique_qptr<Ui::VerticalLayout> _wrap;
|
|
base::Timer _checkUsernameTimer;
|
|
mtpRequestId _checkUsernameRequestId = 0;
|
|
UsernameState _usernameState = UsernameState::Normal;
|
|
|
|
rpl::event_stream<UsernameCheckInfo> _usernameCheckInfo;
|
|
rpl::lifetime _usernameCheckInfoLifetime;
|
|
|
|
rpl::event_stream<int> _scrollToRequests;
|
|
|
|
rpl::lifetime _lifetime;
|
|
|
|
};
|
|
|
|
Controller::Controller(
|
|
Window::SessionNavigation *navigation,
|
|
std::shared_ptr<Ui::BoxShow> show,
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<PeerData*> peer,
|
|
bool useLocationPhrases,
|
|
std::optional<EditPeerTypeData> dataSavedValue)
|
|
: _navigation(navigation)
|
|
, _show(show)
|
|
, _peer(peer)
|
|
, _linkOnly(!dataSavedValue.has_value())
|
|
, _api(&_peer->session().mtp())
|
|
, _dataSavedValue(dataSavedValue)
|
|
, _useLocationPhrases(useLocationPhrases)
|
|
, _isGroup(_peer->isChat() || _peer->isMegagroup())
|
|
, _goodUsername(_dataSavedValue
|
|
? !_dataSavedValue->username.isEmpty()
|
|
: (_peer->isChannel() && !_peer->asChannel()->editableUsername().isEmpty()))
|
|
, _wrap(container)
|
|
, _checkUsernameTimer([=] { checkUsernameAvailability(); }) {
|
|
_peer->updateFull();
|
|
}
|
|
|
|
void Controller::createContent() {
|
|
_controls = Controls();
|
|
|
|
fillPrivaciesButtons(
|
|
_wrap,
|
|
(_dataSavedValue
|
|
? _dataSavedValue->privacy
|
|
: std::optional<Privacy>()));
|
|
|
|
// Skip.
|
|
if (!_linkOnly) {
|
|
_wrap->add(object_ptr<Ui::BoxContentDivider>(_wrap));
|
|
}
|
|
//
|
|
_wrap->add(createInviteLinkBlock());
|
|
if (!_linkOnly) {
|
|
_wrap->add(createUsernameEdit());
|
|
}
|
|
|
|
using namespace Settings;
|
|
|
|
if (!_linkOnly) {
|
|
if (_peer->isMegagroup()) {
|
|
_controls.whoSendWrap = _wrap->add(
|
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
_wrap.get(),
|
|
object_ptr<Ui::VerticalLayout>(_wrap.get())));
|
|
const auto wrap = _controls.whoSendWrap->entity();
|
|
|
|
AddSkip(wrap);
|
|
if (_dataSavedValue->hasLinkedChat) {
|
|
AddSubsectionTitle(wrap, tr::lng_manage_peer_send_title());
|
|
|
|
_controls.joinToWrite = wrap->add(EditPeerInfoBox::CreateButton(
|
|
wrap,
|
|
tr::lng_manage_peer_send_only_members(),
|
|
rpl::single(QString()),
|
|
[=] {},
|
|
st::peerPermissionsButton,
|
|
{}
|
|
));
|
|
_controls.joinToWrite->toggleOn(
|
|
rpl::single(_dataSavedValue->joinToWrite)
|
|
)->toggledValue(
|
|
) | rpl::start_with_next([=](bool toggled) {
|
|
_dataSavedValue->joinToWrite = toggled;
|
|
}, wrap->lifetime());
|
|
} else {
|
|
_controls.whoSendWrap->toggle(
|
|
(_controls.privacy->value() == Privacy::HasUsername),
|
|
anim::type::instant);
|
|
}
|
|
auto joinToWrite = _controls.joinToWrite
|
|
? _controls.joinToWrite->toggledValue()
|
|
: rpl::single(true);
|
|
|
|
const auto requestToJoinWrap = wrap->add(
|
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
|
wrap,
|
|
EditPeerInfoBox::CreateButton(
|
|
wrap,
|
|
tr::lng_manage_peer_send_approve_members(),
|
|
rpl::single(QString()),
|
|
[=] {},
|
|
st::peerPermissionsButton,
|
|
{})))->setDuration(0);
|
|
requestToJoinWrap->toggleOn(rpl::duplicate(joinToWrite));
|
|
_controls.requestToJoin = requestToJoinWrap->entity();
|
|
_controls.requestToJoin->toggleOn(
|
|
rpl::single(_dataSavedValue->requestToJoin)
|
|
)->toggledValue(
|
|
) | rpl::start_with_next([=](bool toggled) {
|
|
_dataSavedValue->requestToJoin = toggled;
|
|
}, wrap->lifetime());
|
|
|
|
AddSkip(wrap);
|
|
AddDividerText(
|
|
wrap,
|
|
rpl::conditional(
|
|
std::move(joinToWrite),
|
|
tr::lng_manage_peer_send_approve_members_about(),
|
|
tr::lng_manage_peer_send_only_members_about()));
|
|
}
|
|
AddSkip(_wrap.get());
|
|
AddSubsectionTitle(
|
|
_wrap.get(),
|
|
tr::lng_manage_peer_no_forwards_title());
|
|
_controls.noForwards = _wrap->add(EditPeerInfoBox::CreateButton(
|
|
_wrap.get(),
|
|
tr::lng_manage_peer_no_forwards(),
|
|
rpl::single(QString()),
|
|
[] {},
|
|
st::peerPermissionsButton,
|
|
{}));
|
|
_controls.noForwards->toggleOn(
|
|
rpl::single(_dataSavedValue->noForwards)
|
|
)->toggledValue(
|
|
) | rpl::start_with_next([=](bool toggled) {
|
|
_dataSavedValue->noForwards = toggled;
|
|
}, _wrap->lifetime());
|
|
AddSkip(_wrap.get());
|
|
AddDividerText(
|
|
_wrap.get(),
|
|
(_isGroup
|
|
? tr::lng_manage_peer_no_forwards_about
|
|
: tr::lng_manage_peer_no_forwards_about_channel)());
|
|
}
|
|
if (_linkOnly) {
|
|
_controls.inviteLinkWrap->show(anim::type::instant);
|
|
} else {
|
|
if (_controls.privacy->value() == Privacy::NoUsername) {
|
|
checkUsernameAvailability();
|
|
}
|
|
const auto forShowing = _dataSavedValue
|
|
? _dataSavedValue->privacy
|
|
: Privacy::NoUsername;
|
|
_controls.inviteLinkWrap->toggle(
|
|
(forShowing != Privacy::HasUsername),
|
|
anim::type::instant);
|
|
_controls.usernameWrap->toggle(
|
|
(forShowing == Privacy::HasUsername),
|
|
anim::type::instant);
|
|
}
|
|
}
|
|
|
|
void Controller::addRoundButton(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Privacy value,
|
|
const QString &text,
|
|
rpl::producer<QString> about) {
|
|
container->add(object_ptr<Ui::Radioenum<Privacy>>(
|
|
container,
|
|
_controls.privacy,
|
|
value,
|
|
text,
|
|
st::editPeerPrivacyBoxCheckbox));
|
|
container->add(object_ptr<Ui::PaddingWrap<Ui::FlatLabel>>(
|
|
container,
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
std::move(about),
|
|
st::editPeerPrivacyLabel),
|
|
st::editPeerPrivacyLabelMargins));
|
|
container->add(object_ptr<Ui::FixedHeightWidget>(
|
|
container,
|
|
st::editPeerPrivacyBottomSkip));
|
|
};
|
|
|
|
void Controller::fillPrivaciesButtons(
|
|
not_null<Ui::VerticalLayout*> parent,
|
|
std::optional<Privacy> savedValue) {
|
|
if (_linkOnly) {
|
|
return;
|
|
}
|
|
|
|
const auto result = parent->add(
|
|
object_ptr<Ui::PaddingWrap<Ui::VerticalLayout>>(
|
|
parent,
|
|
object_ptr<Ui::VerticalLayout>(parent),
|
|
st::editPeerPrivaciesMargins));
|
|
const auto container = result->entity();
|
|
|
|
const auto isPublic = _peer->isChannel()
|
|
&& _peer->asChannel()->hasUsername();
|
|
_controls.privacy = std::make_shared<Ui::RadioenumGroup<Privacy>>(
|
|
savedValue.value_or(
|
|
isPublic ? Privacy::HasUsername : Privacy::NoUsername));
|
|
|
|
addRoundButton(
|
|
container,
|
|
Privacy::HasUsername,
|
|
(_useLocationPhrases
|
|
? tr::lng_create_permanent_link_title
|
|
: _isGroup
|
|
? tr::lng_create_public_group_title
|
|
: tr::lng_create_public_channel_title)(tr::now),
|
|
(_isGroup
|
|
? tr::lng_create_public_group_about
|
|
: tr::lng_create_public_channel_about)());
|
|
addRoundButton(
|
|
container,
|
|
Privacy::NoUsername,
|
|
(_useLocationPhrases
|
|
? tr::lng_create_invite_link_title
|
|
: _isGroup
|
|
? tr::lng_create_private_group_title
|
|
: tr::lng_create_private_channel_title)(tr::now),
|
|
(_useLocationPhrases
|
|
? tr::lng_create_invite_link_about
|
|
: _isGroup
|
|
? tr::lng_create_private_group_about
|
|
: tr::lng_create_private_channel_about)());
|
|
|
|
_controls.privacy->setChangedCallback([=](Privacy value) {
|
|
privacyChanged(value);
|
|
});
|
|
}
|
|
|
|
void Controller::setFocusUsername() {
|
|
if (_controls.usernameInput) {
|
|
_controls.usernameInput->setFocus();
|
|
}
|
|
}
|
|
|
|
QString Controller::getUsernameInput() const {
|
|
return _controls.usernameInput->getLastText().trimmed();
|
|
}
|
|
|
|
std::vector<QString> Controller::usernamesOrder() const {
|
|
return _controls.usernamesList
|
|
? _controls.usernamesList->order()
|
|
: std::vector<QString>();
|
|
}
|
|
|
|
object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
|
|
Expects(_wrap != nullptr);
|
|
|
|
const auto channel = _peer->asChannel();
|
|
const auto username = (!_dataSavedValue || !channel)
|
|
? QString()
|
|
: channel->editableUsername();
|
|
|
|
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
_wrap,
|
|
object_ptr<Ui::VerticalLayout>(_wrap));
|
|
_controls.usernameWrap = result.data();
|
|
|
|
const auto container = result->entity();
|
|
|
|
using namespace Settings;
|
|
AddSkip(container);
|
|
container->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
tr::lng_create_group_link(),
|
|
st::settingsSubsectionTitle),
|
|
st::settingsSubsectionTitlePadding);
|
|
|
|
const auto placeholder = container->add(
|
|
object_ptr<Ui::RpWidget>(container),
|
|
st::editPeerUsernameFieldMargins);
|
|
placeholder->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
_controls.usernameInput = Ui::AttachParentChild(
|
|
container,
|
|
object_ptr<Ui::UsernameInput>(
|
|
container,
|
|
st::setupChannelLink,
|
|
nullptr,
|
|
username,
|
|
_peer->session().createInternalLink(QString())));
|
|
_controls.usernameInput->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.usernameInput->resize(
|
|
width,
|
|
_controls.usernameInput->height());
|
|
}, placeholder->lifetime());
|
|
_controls.usernameInput->move(placeholder->pos());
|
|
|
|
AddUsernameCheckLabel(container, _usernameCheckInfo.events());
|
|
|
|
AddDividerText(
|
|
container,
|
|
tr::lng_create_channel_link_about());
|
|
|
|
if (channel) {
|
|
const auto focusCallback = [=] {
|
|
_scrollToRequests.fire(container->y());
|
|
_controls.usernameInput->setFocusFast();
|
|
};
|
|
_controls.usernamesList = container->add(object_ptr<UsernamesList>(
|
|
container,
|
|
channel,
|
|
_show,
|
|
focusCallback));
|
|
}
|
|
|
|
QObject::connect(
|
|
_controls.usernameInput,
|
|
&Ui::UsernameInput::changed,
|
|
[this] { usernameChanged(); });
|
|
|
|
const auto shown = (_controls.privacy->value() == Privacy::HasUsername);
|
|
result->toggle(shown, anim::type::instant);
|
|
|
|
return result;
|
|
}
|
|
|
|
void Controller::privacyChanged(Privacy value) {
|
|
const auto toggleInviteLink = [&] {
|
|
_controls.inviteLinkWrap->toggle(
|
|
(value != Privacy::HasUsername),
|
|
anim::type::instant);
|
|
};
|
|
const auto toggleEditUsername = [&] {
|
|
_controls.usernameWrap->toggle(
|
|
(value == Privacy::HasUsername),
|
|
anim::type::instant);
|
|
};
|
|
const auto toggleWhoSendWrap = [&] {
|
|
if (!_controls.whoSendWrap) {
|
|
return;
|
|
}
|
|
_controls.whoSendWrap->toggle(
|
|
(value == Privacy::HasUsername
|
|
|| (_dataSavedValue && _dataSavedValue->hasLinkedChat)),
|
|
anim::type::instant);
|
|
};
|
|
const auto refreshVisibilities = [&] {
|
|
// Now first we need to hide that was shown.
|
|
// Otherwise box will change own Y position.
|
|
|
|
if (value == Privacy::HasUsername) {
|
|
toggleInviteLink();
|
|
toggleEditUsername();
|
|
toggleWhoSendWrap();
|
|
|
|
showUsernameEmpty();
|
|
checkUsernameAvailability();
|
|
} else {
|
|
toggleWhoSendWrap();
|
|
toggleEditUsername();
|
|
toggleInviteLink();
|
|
}
|
|
};
|
|
if (value == Privacy::HasUsername) {
|
|
if (_usernameState == UsernameState::TooMany) {
|
|
askUsernameRevoke();
|
|
return;
|
|
} else if (_usernameState == UsernameState::NotAvailable) {
|
|
_controls.privacy->setValue(Privacy::NoUsername);
|
|
return;
|
|
}
|
|
refreshVisibilities();
|
|
_controls.usernameInput->setDisplayFocused(true);
|
|
} else {
|
|
_api.request(base::take(_checkUsernameRequestId)).cancel();
|
|
_checkUsernameTimer.cancel();
|
|
refreshVisibilities();
|
|
}
|
|
setFocusUsername();
|
|
}
|
|
|
|
void Controller::checkUsernameAvailability() {
|
|
if (!_controls.usernameInput) {
|
|
return;
|
|
}
|
|
const auto initial = (_controls.privacy->value() != Privacy::HasUsername);
|
|
const auto checking = initial
|
|
? u".bad."_q
|
|
: getUsernameInput();
|
|
if (checking.size() < Ui::EditPeer::kMinUsernameLength) {
|
|
return;
|
|
}
|
|
if (_checkUsernameRequestId) {
|
|
_api.request(_checkUsernameRequestId).cancel();
|
|
}
|
|
const auto channel = _peer->migrateToOrMe()->asChannel();
|
|
const auto username = channel ? channel->editableUsername() : QString();
|
|
_checkUsernameRequestId = _api.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(tr::lng_create_channel_link_occupied());
|
|
} else {
|
|
showUsernameGood();
|
|
}
|
|
}).fail([=](const MTP::Error &error) {
|
|
_checkUsernameRequestId = 0;
|
|
const auto &type = error.type();
|
|
_usernameState = UsernameState::Normal;
|
|
if (type == u"CHANNEL_PUBLIC_GROUP_NA"_q) {
|
|
_usernameState = UsernameState::NotAvailable;
|
|
_controls.privacy->setValue(Privacy::NoUsername);
|
|
} else if (type == u"CHANNELS_ADMIN_PUBLIC_TOO_MUCH"_q) {
|
|
_usernameState = UsernameState::TooMany;
|
|
if (_controls.privacy->value() == Privacy::HasUsername) {
|
|
askUsernameRevoke();
|
|
}
|
|
} else if (initial) {
|
|
if (_controls.privacy->value() == Privacy::HasUsername) {
|
|
showUsernameEmpty();
|
|
setFocusUsername();
|
|
}
|
|
} else if (type == u"USERNAME_INVALID"_q) {
|
|
showUsernameError(tr::lng_create_channel_link_invalid());
|
|
} else if (type == u"USERNAME_PURCHASE_AVAILABLE"_q) {
|
|
_goodUsername = false;
|
|
_usernameCheckInfo.fire({
|
|
.type = UsernameCheckInfo::Type::PurchaseAvailable,
|
|
});
|
|
} else if (type == u"USERNAME_OCCUPIED"_q && checking != username) {
|
|
showUsernameError(tr::lng_create_channel_link_occupied());
|
|
}
|
|
}).send();
|
|
}
|
|
|
|
void Controller::askUsernameRevoke() {
|
|
_controls.privacy->setValue(Privacy::NoUsername);
|
|
const auto revokeCallback = crl::guard(this, [this] {
|
|
_usernameState = UsernameState::Normal;
|
|
_controls.privacy->setValue(Privacy::HasUsername);
|
|
checkUsernameAvailability();
|
|
});
|
|
_show->showBox(
|
|
Box(PublicLinksLimitBox, _navigation, revokeCallback),
|
|
Ui::LayerOption::KeepOther);
|
|
}
|
|
|
|
void Controller::usernameChanged() {
|
|
_goodUsername = false;
|
|
const auto username = getUsernameInput();
|
|
if (username.isEmpty()) {
|
|
showUsernameEmpty();
|
|
_checkUsernameTimer.cancel();
|
|
return;
|
|
}
|
|
const auto bad = ranges::any_of(username, [](QChar ch) {
|
|
return (ch < 'A' || ch > 'Z')
|
|
&& (ch < 'a' || ch > 'z')
|
|
&& (ch < '0' || ch > '9')
|
|
&& (ch != '_');
|
|
});
|
|
if (bad) {
|
|
showUsernameError(tr::lng_create_channel_link_bad_symbols());
|
|
} else if (username.size() < Ui::EditPeer::kMinUsernameLength) {
|
|
showUsernameError(tr::lng_create_channel_link_too_short());
|
|
} else {
|
|
showUsernamePending();
|
|
_checkUsernameTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
|
|
}
|
|
}
|
|
|
|
void Controller::showUsernameError(rpl::producer<QString> &&error) {
|
|
_goodUsername = false;
|
|
_usernameCheckInfoLifetime.destroy();
|
|
std::move(
|
|
error
|
|
) | rpl::map([](QString s) {
|
|
return UsernameCheckInfo{
|
|
.type = UsernameCheckInfo::Type::Error,
|
|
.text = { std::move(s) },
|
|
};
|
|
}) | rpl::start_to_stream(_usernameCheckInfo, _usernameCheckInfoLifetime);
|
|
}
|
|
|
|
void Controller::showUsernameGood() {
|
|
_goodUsername = true;
|
|
_usernameCheckInfoLifetime.destroy();
|
|
_usernameCheckInfo.fire({
|
|
.type = UsernameCheckInfo::Type::Good,
|
|
.text = { tr::lng_create_channel_link_available(tr::now) },
|
|
});
|
|
}
|
|
|
|
void Controller::showUsernamePending() {
|
|
_usernameCheckInfoLifetime.destroy();
|
|
_usernameCheckInfo.fire({
|
|
.type = UsernameCheckInfo::Type::Default,
|
|
.text = { .text = tr::lng_create_channel_link_pending(tr::now) },
|
|
});
|
|
}
|
|
|
|
void Controller::showUsernameEmpty() {
|
|
_usernameCheckInfoLifetime.destroy();
|
|
_usernameCheckInfo.fire({ .type = UsernameCheckInfo::Type::Default });
|
|
}
|
|
|
|
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {
|
|
Expects(_wrap != nullptr);
|
|
|
|
auto result = object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
_wrap,
|
|
object_ptr<Ui::VerticalLayout>(_wrap));
|
|
_controls.inviteLinkWrap = result.data();
|
|
|
|
const auto container = result->entity();
|
|
|
|
using namespace Settings;
|
|
if (_dataSavedValue) {
|
|
AddSkip(container);
|
|
|
|
AddSubsectionTitle(container, tr::lng_create_permanent_link_title());
|
|
}
|
|
AddPermanentLinkBlock(
|
|
_show,
|
|
container,
|
|
_peer,
|
|
_peer->session().user(),
|
|
nullptr);
|
|
|
|
AddSkip(container);
|
|
|
|
AddDividerText(
|
|
container,
|
|
((_peer->isMegagroup() || _peer->asChat())
|
|
? tr::lng_group_invite_about_permanent_group()
|
|
: tr::lng_group_invite_about_permanent_channel()));
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EditPeerTypeBox::EditPeerTypeBox(
|
|
QWidget*,
|
|
Window::SessionNavigation *navigation,
|
|
not_null<PeerData*> peer,
|
|
bool useLocationPhrases,
|
|
std::optional<FnMut<void(EditPeerTypeData)>> savedCallback,
|
|
std::optional<EditPeerTypeData> dataSaved,
|
|
std::optional<rpl::producer<QString>> usernameError)
|
|
: _navigation(navigation)
|
|
, _peer(peer)
|
|
, _useLocationPhrases(useLocationPhrases)
|
|
, _savedCallback(std::move(savedCallback))
|
|
, _dataSavedValue(dataSaved)
|
|
, _usernameError(usernameError) {
|
|
}
|
|
|
|
EditPeerTypeBox::EditPeerTypeBox(
|
|
QWidget*,
|
|
not_null<PeerData*> peer)
|
|
: EditPeerTypeBox(nullptr, nullptr, peer, {}, {}, {}) {
|
|
}
|
|
|
|
void EditPeerTypeBox::setInnerFocus() {
|
|
_focusRequests.fire({});
|
|
}
|
|
|
|
void EditPeerTypeBox::prepare() {
|
|
_peer->updateFull();
|
|
|
|
auto content = object_ptr<Ui::VerticalLayout>(this);
|
|
|
|
const auto controller = Ui::CreateChild<Controller>(
|
|
this,
|
|
_navigation,
|
|
std::make_shared<Ui::BoxShow>(this),
|
|
content.data(),
|
|
_peer,
|
|
_useLocationPhrases,
|
|
_dataSavedValue);
|
|
controller->scrollToRequests(
|
|
) | rpl::start_with_next([=, raw = content.data()](int y) {
|
|
scrollToY(raw->y() + y);
|
|
}, lifetime());
|
|
_focusRequests.events(
|
|
) | rpl::start_with_next(
|
|
[=] {
|
|
controller->setFocusUsername();
|
|
if (_usernameError.has_value()) {
|
|
controller->showError(std::move(*_usernameError));
|
|
_usernameError = std::nullopt;
|
|
}
|
|
},
|
|
lifetime());
|
|
controller->createContent();
|
|
|
|
setTitle(controller->getTitle());
|
|
|
|
if (_savedCallback.has_value()) {
|
|
addButton(tr::lng_settings_save(), [=] {
|
|
const auto v = controller->getPrivacy();
|
|
if ((v == Privacy::HasUsername) && !controller->goodUsername()) {
|
|
if (!controller->getUsernameInput().isEmpty()
|
|
|| controller->usernamesOrder().empty()) {
|
|
controller->setFocusUsername();
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto local = std::move(*_savedCallback);
|
|
local(EditPeerTypeData{
|
|
.privacy = v,
|
|
.username = (v == Privacy::HasUsername
|
|
? controller->getUsernameInput()
|
|
: QString()),
|
|
.usernamesOrder = (v == Privacy::HasUsername
|
|
? controller->usernamesOrder()
|
|
: std::vector<QString>()),
|
|
.noForwards = controller->noForwards(),
|
|
.joinToWrite = controller->joinToWrite(),
|
|
.requestToJoin = controller->requestToJoin(),
|
|
}); // We don't need username with private type.
|
|
closeBox();
|
|
});
|
|
addButton(tr::lng_cancel(), [=] { closeBox(); });
|
|
} else {
|
|
addButton(tr::lng_close(), [=] { closeBox(); });
|
|
}
|
|
|
|
setDimensionsToContent(st::boxWideWidth, content.data());
|
|
setInnerWidget(std::move(content));
|
|
}
|