username_box.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "boxes/username_box.h"
  8. #include "boxes/peers/edit_peer_usernames_list.h"
  9. #include "base/timer.h"
  10. #include "boxes/peers/edit_peer_common.h"
  11. #include "data/data_channel.h"
  12. #include "data/data_session.h"
  13. #include "data/data_user.h"
  14. #include "lang/lang_keys.h"
  15. #include "main/main_app_config_values.h"
  16. #include "main/main_session.h"
  17. #include "mtproto/sender.h"
  18. #include "ui/layers/generic_box.h"
  19. #include "ui/painter.h"
  20. #include "ui/vertical_list.h"
  21. #include "ui/text/text_utilities.h"
  22. #include "ui/text/text_variant.h"
  23. #include "ui/toast/toast.h"
  24. #include "ui/widgets/buttons.h"
  25. #include "ui/widgets/fields/special_fields.h"
  26. #include "ui/widgets/labels.h"
  27. #include "ui/wrap/follow_slide_wrap.h"
  28. #include "ui/wrap/slide_wrap.h"
  29. #include "styles/style_layers.h"
  30. #include "styles/style_boxes.h"
  31. namespace {
  32. class UsernameEditor final : public Ui::RpWidget {
  33. public:
  34. UsernameEditor(not_null<Ui::RpWidget*>, not_null<PeerData*> peer);
  35. void setInnerFocus();
  36. void setEnabled(bool value);
  37. [[nodiscard]] rpl::producer<> submitted() const;
  38. [[nodiscard]] rpl::producer<> save();
  39. [[nodiscard]] rpl::producer<UsernameCheckInfo> checkInfoChanged() const;
  40. protected:
  41. void resizeEvent(QResizeEvent *e) override;
  42. private:
  43. void updateFail(const QString &error);
  44. void checkFail(const QString &error);
  45. void checkInfoPurchaseAvailable();
  46. void check();
  47. void changed();
  48. void checkInfoChange();
  49. [[nodiscard]] QString editableUsername() const;
  50. QString getName() const;
  51. const not_null<PeerData*> _peer;
  52. const not_null<Main::Session*> _session;
  53. const style::margins &_padding;
  54. MTP::Sender _api;
  55. object_ptr<Ui::UsernameInput> _username;
  56. mtpRequestId _saveRequestId = 0;
  57. mtpRequestId _checkRequestId = 0;
  58. QString _sentUsername, _checkUsername, _errorText, _goodText;
  59. base::Timer _checkTimer;
  60. rpl::event_stream<> _saved;
  61. rpl::event_stream<UsernameCheckInfo> _checkInfoChanged;
  62. };
  63. UsernameEditor::UsernameEditor(
  64. not_null<Ui::RpWidget*>,
  65. not_null<PeerData*> peer)
  66. : _peer(peer)
  67. , _session(&peer->session())
  68. , _padding(st::usernamePadding)
  69. , _api(&_session->mtp())
  70. , _username(
  71. this,
  72. st::defaultInputField,
  73. rpl::single(u"@username"_q),
  74. editableUsername(),
  75. QString())
  76. , _checkTimer([=] { check(); }) {
  77. _goodText = editableUsername().isEmpty()
  78. ? QString()
  79. : tr::lng_username_available(tr::now);
  80. connect(_username, &Ui::MaskedInputField::changed, [=] { changed(); });
  81. resize(width(), (_padding.top() + _username->height()));
  82. }
  83. rpl::producer<> UsernameEditor::submitted() const {
  84. return [=](auto consumer) {
  85. auto lifetime = rpl::lifetime();
  86. QObject::connect(
  87. _username,
  88. &Ui::MaskedInputField::submitted,
  89. [=] { consumer.put_next({}); });
  90. return lifetime;
  91. };
  92. }
  93. void UsernameEditor::setInnerFocus() {
  94. if (_username->isEnabled()) {
  95. _username->setFocusFast();
  96. }
  97. }
  98. void UsernameEditor::setEnabled(bool value) {
  99. _username->setEnabled(value);
  100. _username->setDisplayFocused(value);
  101. }
  102. void UsernameEditor::resizeEvent(QResizeEvent *e) {
  103. _username->resize(
  104. width() - _padding.left() - _padding.right(),
  105. _username->height());
  106. _username->moveToLeft(_padding.left(), _padding.top());
  107. }
  108. rpl::producer<> UsernameEditor::save() {
  109. if (_saveRequestId) {
  110. return _saved.events();
  111. }
  112. _sentUsername = getName();
  113. _saveRequestId = _api.request(MTPaccount_UpdateUsername(
  114. MTP_string(_sentUsername)
  115. )).done([=](const MTPUser &result) {
  116. _saveRequestId = 0;
  117. _session->data().processUser(result);
  118. _saved.fire_done();
  119. }).fail([=](const MTP::Error &error) {
  120. _saveRequestId = 0;
  121. updateFail(error.type());
  122. }).send();
  123. return _saved.events();
  124. }
  125. QString UsernameEditor::editableUsername() const {
  126. if (const auto user = _peer->asUser()) {
  127. return user->editableUsername();
  128. } else if (const auto channel = _peer->asChannel()) {
  129. return channel->editableUsername();
  130. } else {
  131. return QString();
  132. }
  133. }
  134. rpl::producer<UsernameCheckInfo> UsernameEditor::checkInfoChanged() const {
  135. return _checkInfoChanged.events();
  136. }
  137. void UsernameEditor::check() {
  138. _api.request(base::take(_checkRequestId)).cancel();
  139. const auto name = getName();
  140. if (name.size() < Ui::EditPeer::kMinUsernameLength) {
  141. return;
  142. }
  143. _checkUsername = name;
  144. _checkRequestId = _api.request(MTPaccount_CheckUsername(
  145. MTP_string(name)
  146. )).done([=](const MTPBool &result) {
  147. _checkRequestId = 0;
  148. _errorText = (mtpIsTrue(result)
  149. || (_checkUsername == editableUsername()))
  150. ? QString()
  151. : tr::lng_username_occupied(tr::now);
  152. _goodText = _errorText.isEmpty()
  153. ? tr::lng_username_available(tr::now)
  154. : QString();
  155. checkInfoChange();
  156. }).fail([=](const MTP::Error &error) {
  157. _checkRequestId = 0;
  158. checkFail(error.type());
  159. }).send();
  160. }
  161. void UsernameEditor::changed() {
  162. const auto name = getName();
  163. if (name.isEmpty()) {
  164. if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
  165. _errorText = _goodText = QString();
  166. _checkInfoChanged.fire({ UsernameCheckInfo::Type::Default });
  167. }
  168. _checkTimer.cancel();
  169. } else {
  170. const auto len = int(name.size());
  171. for (auto i = 0; i < len; ++i) {
  172. const auto ch = name.at(i);
  173. if ((ch < 'A' || ch > 'Z')
  174. && (ch < 'a' || ch > 'z')
  175. && (ch < '0' || ch > '9')
  176. && ch != '_'
  177. && (ch != '@' || i > 0)) {
  178. if (_errorText != tr::lng_username_bad_symbols(tr::now)) {
  179. _errorText = tr::lng_username_bad_symbols(tr::now);
  180. checkInfoChange();
  181. }
  182. _checkTimer.cancel();
  183. return;
  184. }
  185. }
  186. if (name.size() < Ui::EditPeer::kMinUsernameLength) {
  187. if (_errorText != tr::lng_username_too_short(tr::now)) {
  188. _errorText = tr::lng_username_too_short(tr::now);
  189. checkInfoChange();
  190. }
  191. _checkTimer.cancel();
  192. } else {
  193. if (!_errorText.isEmpty() || !_goodText.isEmpty()) {
  194. _errorText = _goodText = QString();
  195. checkInfoChange();
  196. }
  197. _checkTimer.callOnce(Ui::EditPeer::kUsernameCheckTimeout);
  198. }
  199. }
  200. }
  201. void UsernameEditor::checkInfoChange() {
  202. if (!_errorText.isEmpty()) {
  203. _checkInfoChanged.fire({
  204. .type = UsernameCheckInfo::Type::Error,
  205. .text = { _errorText },
  206. });
  207. } else if (!_goodText.isEmpty()) {
  208. _checkInfoChanged.fire({
  209. .type = UsernameCheckInfo::Type::Good,
  210. .text = { _goodText },
  211. });
  212. } else {
  213. _checkInfoChanged.fire({
  214. .type = UsernameCheckInfo::Type::Default,
  215. .text = { tr::lng_username_choose(tr::now) },
  216. });
  217. }
  218. }
  219. void UsernameEditor::checkInfoPurchaseAvailable() {
  220. _username->setFocus();
  221. _username->showError();
  222. _errorText = u".bad."_q;
  223. _checkInfoChanged.fire(
  224. UsernameCheckInfo::PurchaseAvailable(_checkUsername, _peer));
  225. }
  226. void UsernameEditor::updateFail(const QString &error) {
  227. if ((error == u"USERNAME_NOT_MODIFIED"_q)
  228. || (_sentUsername == editableUsername())) {
  229. if (const auto user = _peer->asUser()) {
  230. user->setName(
  231. TextUtilities::SingleLine(user->firstName),
  232. TextUtilities::SingleLine(user->lastName),
  233. TextUtilities::SingleLine(user->nameOrPhone),
  234. TextUtilities::SingleLine(_sentUsername));
  235. }
  236. _saved.fire_done();
  237. } else if (error == u"USERNAME_INVALID"_q) {
  238. _username->setFocus();
  239. _username->showError();
  240. _errorText = tr::lng_username_invalid(tr::now);
  241. checkInfoChange();
  242. } else if ((error == u"USERNAME_OCCUPIED"_q)
  243. || (error == u"USERNAMES_UNAVAILABLE"_q)) {
  244. _username->setFocus();
  245. _username->showError();
  246. _errorText = tr::lng_username_occupied(tr::now);
  247. checkInfoChange();
  248. } else if (error == u"USERNAME_PURCHASE_AVAILABLE"_q) {
  249. checkInfoPurchaseAvailable();
  250. } else {
  251. _username->setFocus();
  252. }
  253. }
  254. void UsernameEditor::checkFail(const QString &error) {
  255. if (error == u"USERNAME_INVALID"_q) {
  256. _errorText = tr::lng_username_invalid(tr::now);
  257. checkInfoChange();
  258. } else if ((error == u"USERNAME_OCCUPIED"_q)
  259. && (_checkUsername != editableUsername())) {
  260. _errorText = tr::lng_username_occupied(tr::now);
  261. checkInfoChange();
  262. } else if (error == u"USERNAME_PURCHASE_AVAILABLE"_q) {
  263. checkInfoPurchaseAvailable();
  264. } else {
  265. _goodText = QString();
  266. _username->setFocus();
  267. }
  268. }
  269. QString UsernameEditor::getName() const {
  270. return _username->text().replace('@', QString()).trimmed();
  271. }
  272. } // namespace
  273. void UsernamesBox(
  274. not_null<Ui::GenericBox*> box,
  275. not_null<PeerData*> peer) {
  276. const auto isBot = peer && peer->isUser() && peer->asUser()->isBot();
  277. box->setTitle(isBot
  278. ? tr::lng_bot_username_title()
  279. : tr::lng_username_title());
  280. const auto container = box->verticalLayout();
  281. const auto editor = box->addRow(
  282. object_ptr<UsernameEditor>(box, peer),
  283. {});
  284. editor->setEnabled(!isBot);
  285. box->setFocusCallback([=] { editor->setInnerFocus(); });
  286. AddUsernameCheckLabel(container, editor->checkInfoChanged());
  287. auto description = [&]() -> rpl::producer<TextWithEntities> {
  288. if (!isBot) {
  289. return rpl::combine(
  290. tr::lng_username_description1(Ui::Text::RichLangValue),
  291. tr::lng_username_description2(Ui::Text::RichLangValue)
  292. ) | rpl::map([](TextWithEntities d1, TextWithEntities d2) {
  293. return d1.append("\n\n").append(std::move(d2));
  294. });
  295. }
  296. if (const auto url = AppConfig::FragmentLink(&peer->session())) {
  297. const auto link = Ui::Text::Link(
  298. tr::lng_bot_username_description1_link(tr::now),
  299. *url);
  300. return tr::lng_bot_username_description1(
  301. lt_link,
  302. rpl::single(link),
  303. Ui::Text::RichLangValue);
  304. }
  305. return rpl::single<TextWithEntities>({});
  306. }();
  307. container->add(object_ptr<Ui::DividerLabel>(
  308. container,
  309. object_ptr<Ui::FlatLabel>(
  310. container,
  311. std::move(description),
  312. st::boxDividerLabel),
  313. st::defaultBoxDividerLabelPadding));
  314. const auto list = box->addRow(
  315. object_ptr<UsernamesList>(
  316. box,
  317. peer,
  318. box->uiShow(),
  319. !isBot
  320. ? [=] { box->scrollToY(0); editor->setInnerFocus(); }
  321. : Fn<void()>(nullptr)),
  322. {});
  323. const auto finish = [=] {
  324. list->save(
  325. ) | rpl::start_with_done([=] {
  326. editor->save(
  327. ) | rpl::start_with_done([=] {
  328. box->closeBox();
  329. }, box->lifetime());
  330. }, box->lifetime());
  331. };
  332. editor->submitted(
  333. ) | rpl::start_with_next(finish, editor->lifetime());
  334. if (isBot) {
  335. box->addButton(tr::lng_close(), [=] { box->closeBox(); });
  336. } else {
  337. box->addButton(tr::lng_settings_save(), finish);
  338. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  339. }
  340. }
  341. void AddUsernameCheckLabel(
  342. not_null<Ui::VerticalLayout*> container,
  343. rpl::producer<UsernameCheckInfo> checkInfo) {
  344. const auto padding = st::boxRowPadding;
  345. const auto &st = st::aboutRevokePublicLabel;
  346. const auto skip = (st::usernameSkip - st.style.font->height) / 4;
  347. auto wrapped = object_ptr<Ui::VerticalLayout>(container);
  348. Ui::AddSkip(wrapped, skip);
  349. const auto label = wrapped->add(object_ptr<Ui::FlatLabel>(wrapped, st));
  350. Ui::AddSkip(wrapped, skip);
  351. Ui::AddSkip(container, skip);
  352. container->add(
  353. object_ptr<Ui::FollowSlideWrap<Ui::VerticalLayout>>(
  354. container,
  355. std::move(wrapped)),
  356. padding);
  357. rpl::combine(
  358. std::move(checkInfo),
  359. container->widthValue()
  360. ) | rpl::start_with_next([=](const UsernameCheckInfo &info, int w) {
  361. using Type = UsernameCheckInfo::Type;
  362. label->setMarkedText(info.text);
  363. const auto &color = (info.type == Type::Good)
  364. ? st::boxTextFgGood
  365. : (info.type == Type::Error)
  366. ? st::boxTextFgError
  367. : st::usernameDefaultFg;
  368. label->setTextColorOverride(color->c);
  369. label->resizeToWidth(w - padding.left() - padding.right());
  370. }, label->lifetime());
  371. Ui::AddSkip(container, skip);
  372. }
  373. UsernameCheckInfo UsernameCheckInfo::PurchaseAvailable(
  374. const QString &username,
  375. not_null<PeerData*> peer) {
  376. if (const auto fragmentLink = AppConfig::FragmentLink(&peer->session())) {
  377. return {
  378. .type = UsernameCheckInfo::Type::Default,
  379. .text = tr::lng_username_purchase_available(
  380. tr::now,
  381. lt_link,
  382. Ui::Text::Link(
  383. tr::lng_username_purchase_available_link(tr::now),
  384. (*fragmentLink) + u"/username/"_q + username),
  385. Ui::Text::RichLangValue),
  386. };
  387. } else {
  388. return {
  389. .type = UsernameCheckInfo::Type::Error,
  390. .text = { u"INTERNAL_SERVER_ERROR"_q },
  391. };
  392. }
  393. }