Respected new error for occupied usernames in manage channel.

This commit is contained in:
23rd 2022-11-27 15:32:13 +03:00
parent 2acedca6b7
commit 3fdb807a1e
7 changed files with 126 additions and 114 deletions

View file

@ -1386,6 +1386,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_create_channel_link_too_short" = "Sorry, this link is too short"; "lng_create_channel_link_too_short" = "Sorry, this link is too short";
"lng_create_channel_link_bad_symbols" = "Only 0-9, a-z, and underscores allowed."; "lng_create_channel_link_bad_symbols" = "Only 0-9, a-z, and underscores allowed.";
"lng_create_channel_link_available" = "This link is available"; "lng_create_channel_link_available" = "This link is available";
"lng_create_channel_link_pending" = "Checking name...";
"lng_create_channel_link_copied" = "Link copied to clipboard"; "lng_create_channel_link_copied" = "Link copied to clipboard";
"lng_create_group_crop" = "Select an area for group photo"; "lng_create_group_crop" = "Select an area for group photo";

View file

@ -1225,6 +1225,8 @@ SetupChannelBox::UsernameResult SetupChannelBox::parseError(
return UsernameResult::Invalid; return UsernameResult::Invalid;
} else if (error == u"USERNAME_OCCUPIED"_q) { } else if (error == u"USERNAME_OCCUPIED"_q) {
return UsernameResult::Occupied; return UsernameResult::Occupied;
} else if (error == u"USERNAME_PURCHASE_AVAILABLE"_q) {
return UsernameResult::Occupied;
} else if (error == u"USERNAMES_UNAVAILABLE"_q) { } else if (error == u"USERNAMES_UNAVAILABLE"_q) {
return UsernameResult::Occupied; return UsernameResult::Occupied;
} else if (error == u"CHANNEL_PUBLIC_GROUP_NA"_q) { } else if (error == u"CHANNEL_PUBLIC_GROUP_NA"_q) {

View file

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_peer_invite_link.h" #include "boxes/peers/edit_peer_invite_link.h"
#include "boxes/peers/edit_peer_invite_links.h" #include "boxes/peers/edit_peer_invite_links.h"
#include "boxes/peers/edit_peer_usernames_list.h" #include "boxes/peers/edit_peer_usernames_list.h"
#include "boxes/username_box.h"
#include "chat_helpers/emoji_suggestions_widget.h" #include "chat_helpers/emoji_suggestions_widget.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_chat.h" #include "data/data_chat.h"
@ -103,8 +104,7 @@ private:
Ui::SlideWrap<Ui::RpWidget> *usernameWrap = nullptr; Ui::SlideWrap<Ui::RpWidget> *usernameWrap = nullptr;
Ui::UsernameInput *usernameInput = nullptr; Ui::UsernameInput *usernameInput = nullptr;
UsernamesList *usernamesList = nullptr; UsernamesList *usernamesList = nullptr;
base::unique_qptr<Ui::FlatLabel> usernameResult; base::unique_qptr<Ui::FlatLabel> usernameCheckResult;
const style::FlatLabel *usernameResultStyle = nullptr;
Ui::SlideWrap<> *inviteLinkWrap = nullptr; Ui::SlideWrap<> *inviteLinkWrap = nullptr;
Ui::FlatLabel *inviteLink = nullptr; Ui::FlatLabel *inviteLink = nullptr;
@ -127,9 +127,8 @@ private:
void usernameChanged(); void usernameChanged();
void showUsernameError(rpl::producer<QString> &&error); void showUsernameError(rpl::producer<QString> &&error);
void showUsernameGood(); void showUsernameGood();
void showUsernameResult( void showUsernamePending();
rpl::producer<QString> &&text, void showUsernameEmpty();
not_null<const style::FlatLabel*> st);
void fillPrivaciesButtons( void fillPrivaciesButtons(
not_null<Ui::VerticalLayout*> parent, not_null<Ui::VerticalLayout*> parent,
@ -157,7 +156,9 @@ private:
base::Timer _checkUsernameTimer; base::Timer _checkUsernameTimer;
mtpRequestId _checkUsernameRequestId = 0; mtpRequestId _checkUsernameRequestId = 0;
UsernameState _usernameState = UsernameState::Normal; UsernameState _usernameState = UsernameState::Normal;
rpl::event_stream<rpl::producer<QString>> _usernameResultTexts;
rpl::event_stream<UsernameCheckInfo> _usernameCheckInfo;
rpl::lifetime _usernameCheckInfoLifetime;
rpl::event_stream<int> _scrollToRequests; rpl::event_stream<int> _scrollToRequests;
@ -450,6 +451,8 @@ object_ptr<Ui::RpWidget> Controller::createUsernameEdit() {
}, placeholder->lifetime()); }, placeholder->lifetime());
_controls.usernameInput->move(placeholder->pos()); _controls.usernameInput->move(placeholder->pos());
AddUsernameCheckLabel(container, _usernameCheckInfo.events());
AddDividerText( AddDividerText(
container, container,
tr::lng_create_channel_link_about()); tr::lng_create_channel_link_about());
@ -506,7 +509,7 @@ void Controller::privacyChanged(Privacy value) {
toggleEditUsername(); toggleEditUsername();
toggleWhoSendWrap(); toggleWhoSendWrap();
_controls.usernameResult = nullptr; showUsernameEmpty();
checkUsernameAvailability(); checkUsernameAvailability();
} else { } else {
toggleWhoSendWrap(); toggleWhoSendWrap();
@ -575,11 +578,16 @@ void Controller::checkUsernameAvailability() {
} }
} else if (initial) { } else if (initial) {
if (_controls.privacy->value() == Privacy::HasUsername) { if (_controls.privacy->value() == Privacy::HasUsername) {
_controls.usernameResult = nullptr; showUsernameEmpty();
setFocusUsername(); setFocusUsername();
} }
} else if (type == u"USERNAME_INVALID"_q) { } else if (type == u"USERNAME_INVALID"_q) {
showUsernameError(tr::lng_create_channel_link_invalid()); 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) { } else if (type == u"USERNAME_OCCUPIED"_q && checking != username) {
showUsernameError(tr::lng_create_channel_link_occupied()); showUsernameError(tr::lng_create_channel_link_occupied());
} }
@ -602,7 +610,7 @@ void Controller::usernameChanged() {
_goodUsername = false; _goodUsername = false;
const auto username = getUsernameInput(); const auto username = getUsernameInput();
if (username.isEmpty()) { if (username.isEmpty()) {
_controls.usernameResult = nullptr; showUsernameEmpty();
_checkUsernameTimer.cancel(); _checkUsernameTimer.cancel();
return; return;
} }
@ -617,43 +625,44 @@ void Controller::usernameChanged() {
} else if (username.size() < Ui::EditPeer::kMinUsernameLength) { } else if (username.size() < Ui::EditPeer::kMinUsernameLength) {
showUsernameError(tr::lng_create_channel_link_too_short()); showUsernameError(tr::lng_create_channel_link_too_short());
} else { } else {
_controls.usernameResult = nullptr; showUsernamePending();
_checkUsernameTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout); _checkUsernameTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
} }
} }
void Controller::showUsernameError(rpl::producer<QString> &&error) { void Controller::showUsernameError(rpl::producer<QString> &&error) {
_goodUsername = false; _goodUsername = false;
showUsernameResult(std::move(error), &st::editPeerUsernameError); _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() { void Controller::showUsernameGood() {
_goodUsername = true; _goodUsername = true;
showUsernameResult( _usernameCheckInfoLifetime.destroy();
tr::lng_create_channel_link_available(), _usernameCheckInfo.fire({
&st::editPeerUsernameGood); .type = UsernameCheckInfo::Type::Good,
.text = { tr::lng_create_channel_link_available(tr::now) },
});
} }
void Controller::showUsernameResult( void Controller::showUsernamePending() {
rpl::producer<QString> &&text, _usernameCheckInfoLifetime.destroy();
not_null<const style::FlatLabel*> st) { _usernameCheckInfo.fire({
if (!_controls.usernameResult .type = UsernameCheckInfo::Type::Default,
|| _controls.usernameResultStyle != st) { .text = { .text = tr::lng_create_channel_link_pending(tr::now) },
_controls.usernameResultStyle = st; });
_controls.usernameResult = base::make_unique_q<Ui::FlatLabel>( }
_controls.usernameWrap,
_usernameResultTexts.events() | rpl::flatten_latest(), void Controller::showUsernameEmpty() {
*st); _usernameCheckInfoLifetime.destroy();
const auto label = _controls.usernameResult.get(); _usernameCheckInfo.fire({ .type = UsernameCheckInfo::Type::Default });
label->show();
label->widthValue(
) | rpl::start_with_next([label] {
label->moveToRight(
st::editPeerUsernamePosition.x(),
st::editPeerUsernamePosition.y());
}, label->lifetime());
}
_usernameResultTexts.fire(std::move(text));
} }
object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() { object_ptr<Ui::RpWidget> Controller::createInviteLinkBlock() {

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/fields/special_fields.h" #include "ui/widgets/fields/special_fields.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
#include "ui/wrap/follow_slide_wrap.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
@ -31,16 +32,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace { namespace {
struct CheckInfo final { [[nodiscard]] TextWithEntities PurchaseAvailableText() {
enum class Type { constexpr auto kUsernameAuction = "auction";
Good, return tr::lng_username_purchase_available(
Error, tr::now,
Default, lt_link,
PurchaseAvailable, Ui::Text::Link(
}; '@' + QString(kUsernameAuction),
Type type; u"https://t.me/"_q + kUsernameAuction),
v::text::data text; Ui::Text::RichLangValue);
}; }
class UsernameEditor final : public Ui::RpWidget { class UsernameEditor final : public Ui::RpWidget {
public: public:
@ -49,7 +50,7 @@ public:
void setInnerFocus(); void setInnerFocus();
[[nodiscard]] rpl::producer<> submitted() const; [[nodiscard]] rpl::producer<> submitted() const;
[[nodiscard]] rpl::producer<> save(); [[nodiscard]] rpl::producer<> save();
[[nodiscard]] rpl::producer<CheckInfo> checkInfoChanged() const; [[nodiscard]] rpl::producer<UsernameCheckInfo> checkInfoChanged() const;
protected: protected:
void resizeEvent(QResizeEvent *e) override; void resizeEvent(QResizeEvent *e) override;
@ -80,7 +81,7 @@ private:
base::Timer _checkTimer; base::Timer _checkTimer;
rpl::event_stream<> _saved; rpl::event_stream<> _saved;
rpl::event_stream<CheckInfo> _checkInfoChanged; rpl::event_stream<UsernameCheckInfo> _checkInfoChanged;
}; };
@ -147,7 +148,7 @@ rpl::producer<> UsernameEditor::save() {
return _saved.events(); return _saved.events();
} }
rpl::producer<CheckInfo> UsernameEditor::checkInfoChanged() const { rpl::producer<UsernameCheckInfo> UsernameEditor::checkInfoChanged() const {
return _checkInfoChanged.events(); return _checkInfoChanged.events();
} }
@ -184,7 +185,7 @@ void UsernameEditor::changed() {
if (name.isEmpty()) { if (name.isEmpty()) {
if (!_errorText.isEmpty() || !_goodText.isEmpty()) { if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
_errorText = _goodText = QString(); _errorText = _goodText = QString();
checkInfoChange(); _checkInfoChanged.fire({ UsernameCheckInfo::Type::Default });
} }
_checkTimer.cancel(); _checkTimer.cancel();
} else { } else {
@ -223,38 +224,29 @@ void UsernameEditor::changed() {
void UsernameEditor::checkInfoChange() { void UsernameEditor::checkInfoChange() {
if (!_errorText.isEmpty()) { if (!_errorText.isEmpty()) {
_checkInfoChanged.fire({ _checkInfoChanged.fire({
.type = CheckInfo::Type::Error, .type = UsernameCheckInfo::Type::Error,
.text = _errorText, .text = { _errorText },
}); });
} else if (!_goodText.isEmpty()) { } else if (!_goodText.isEmpty()) {
_checkInfoChanged.fire({ _checkInfoChanged.fire({
.type = CheckInfo::Type::Good, .type = UsernameCheckInfo::Type::Good,
.text = _goodText, .text = { _goodText },
}); });
} else { } else {
_checkInfoChanged.fire({ _checkInfoChanged.fire({
.type = CheckInfo::Type::Default, .type = UsernameCheckInfo::Type::Default,
.text = tr::lng_username_choose(tr::now), .text = { tr::lng_username_choose(tr::now) },
}); });
} }
} }
void UsernameEditor::checkInfoPurchaseAvailable() { void UsernameEditor::checkInfoPurchaseAvailable() {
constexpr auto kUsernameAuction = "auction";
const auto text = tr::lng_username_purchase_available(
tr::now,
lt_link,
Ui::Text::Link(
'@' + QString(kUsernameAuction),
u"https://t.me/"_q + kUsernameAuction),
Ui::Text::RichLangValue);
_username->setFocus(); _username->setFocus();
_username->showError(); _username->showError();
_errorText = text.text; _errorText = u".bad."_q;
_checkInfoChanged.fire({ _checkInfoChanged.fire({
.type = CheckInfo::Type::PurchaseAvailable, .type = UsernameCheckInfo::Type::PurchaseAvailable,
.text = text,
}); });
} }
@ -320,52 +312,7 @@ void UsernamesBox(
{}); {});
box->setFocusCallback([=] { editor->setInnerFocus(); }); box->setFocusCallback([=] { editor->setInnerFocus(); });
{ AddUsernameCheckLabel(container, editor->checkInfoChanged());
const auto padding = st::boxRowPadding;
const auto &st = st::aboutRevokePublicLabel;
const auto skip = (st::usernameSkip - st.style.font->height) / 2;
box->addSkip(skip);
const auto wrap = box->addRow(
object_ptr<Ui::SlideWrap<Ui::RpWidget>>(
box,
object_ptr<Ui::RpWidget>(box)),
padding);
wrap->setMinimalHeight(st.style.font->height);
const auto maxHeightWrap = wrap->entity();
const auto label = Ui::CreateChild<Ui::FlatLabel>(
maxHeightWrap,
editor->checkInfoChanged(
) | rpl::map([](CheckInfo info) {
return v::text::take_marked(base::take(info.text));
}) | rpl::flatten_latest(),
st);
label->heightValue(
) | rpl::start_with_next([=](int height) {
if (height > maxHeightWrap->height()) {
maxHeightWrap->resize(maxHeightWrap->width(), height);
}
}, maxHeightWrap->lifetime());
editor->checkInfoChanged(
) | rpl::start_with_next([=](CheckInfo info) {
const auto &color = (info.type == CheckInfo::Type::Good)
? st::boxTextFgGood
: (info.type == CheckInfo::Type::Error)
? st::boxTextFgError
: st::usernameDefaultFg;
label->setTextColorOverride(color->c);
label->resizeToWidth(container->width()
- padding.left()
- padding.right());
wrap->toggle(
info.type == CheckInfo::Type::PurchaseAvailable,
anim::type::normal);
}, label->lifetime());
box->addSkip(skip);
}
container->add(object_ptr<Ui::DividerLabel>( container->add(object_ptr<Ui::DividerLabel>(
container, container,
@ -401,3 +348,41 @@ void UsernamesBox(
box->addButton(tr::lng_settings_save(), finish); box->addButton(tr::lng_settings_save(), finish);
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
} }
void AddUsernameCheckLabel(
not_null<Ui::VerticalLayout*> container,
rpl::producer<UsernameCheckInfo> checkInfo) {
const auto padding = st::boxRowPadding;
const auto &st = st::aboutRevokePublicLabel;
const auto skip = (st::usernameSkip - st.style.font->height) / 4;
auto wrapped = object_ptr<Ui::VerticalLayout>(container);
Settings::AddSkip(wrapped, skip);
const auto label = wrapped->add(object_ptr<Ui::FlatLabel>(wrapped, st));
Settings::AddSkip(wrapped, skip);
Settings::AddSkip(container, skip);
const auto wrap = container->add(
object_ptr<Ui::FollowSlideWrap<Ui::VerticalLayout>>(
container,
std::move(wrapped)),
padding);
rpl::combine(
std::move(checkInfo),
container->widthValue()
) | rpl::start_with_next([=](const UsernameCheckInfo &info, int w) {
using Type = UsernameCheckInfo::Type;
label->setMarkedText((info.type == Type::PurchaseAvailable)
? PurchaseAvailableText()
: info.text);
const auto &color = (info.type == Type::Good)
? st::boxTextFgGood
: (info.type == Type::Error)
? st::boxTextFgError
: st::usernameDefaultFg;
label->setTextColorOverride(color->c);
label->resizeToWidth(w - padding.left() - padding.right());
}, label->lifetime());
Settings::AddSkip(container, skip);
}

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui { namespace Ui {
class GenericBox; class GenericBox;
class VerticalLayout;
} // namespace Ui } // namespace Ui
namespace Main { namespace Main {
@ -18,3 +19,18 @@ class Session;
void UsernamesBox( void UsernamesBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session); not_null<Main::Session*> session);
struct UsernameCheckInfo final {
enum class Type {
Good,
Error,
Default,
PurchaseAvailable,
};
Type type;
TextWithEntities text;
};
void AddUsernameCheckLabel(
not_null<Ui::VerticalLayout*> container,
rpl::producer<UsernameCheckInfo> checkInfo);

View file

@ -655,7 +655,7 @@ editPeerHistoryVisibilityLabelMargins: margins(34px, 0px, 48px, 0px);
editPeerPrivacyLabelMargins: margins(42px, 0px, 34px, 0px); editPeerPrivacyLabelMargins: margins(42px, 0px, 34px, 0px);
editPeerPreHistoryLabelMargins: margins(34px, 0px, 34px, 0px); editPeerPreHistoryLabelMargins: margins(34px, 0px, 34px, 0px);
editPeerUsernameTitleLabelMargins: margins(22px, 17px, 22px, 10px); editPeerUsernameTitleLabelMargins: margins(22px, 17px, 22px, 10px);
editPeerUsernameFieldMargins: margins(22px, 0px, 22px, 16px); editPeerUsernameFieldMargins: margins(22px, 0px, 22px, 0px);
editPeerUsername: setupChannelLink; editPeerUsername: setupChannelLink;
editPeerUsernameSkip: 8px; editPeerUsernameSkip: 8px;
editPeerInviteLink: FlatLabel(defaultFlatLabel) { editPeerInviteLink: FlatLabel(defaultFlatLabel) {
@ -669,7 +669,6 @@ editPeerUsernameGood: FlatLabel(defaultFlatLabel) {
editPeerUsernameError: FlatLabel(editPeerUsernameGood) { editPeerUsernameError: FlatLabel(editPeerUsernameGood) {
textFg: boxTextFgError; textFg: boxTextFgError;
} }
editPeerUsernamePosition: point(22px, 18px);
editPeerReactionsButton: SettingsButton(infoProfileButton) { editPeerReactionsButton: SettingsButton(infoProfileButton) {
padding: margins(59px, 13px, 8px, 11px); padding: margins(59px, 13px, 8px, 11px);

@ -1 +1 @@
Subproject commit 0937ac0ad0feaf3cbccf009e0e1f2a04cc0ef9f8 Subproject commit c197c1831d1af48cf769f271a126af2c2eaf8185