phone_click_handler.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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 "core/phone_click_handler.h"
  8. #include "boxes/add_contact_box.h"
  9. #include "core/click_handler_types.h"
  10. #include "data/data_session.h"
  11. #include "data/data_user.h"
  12. #include "info/profile/info_profile_values.h"
  13. #include "lang/lang_keys.h"
  14. #include "main/main_session.h"
  15. #include "mainwidget.h"
  16. #include "mtproto/sender.h"
  17. #include "ui/effects/ripple_animation.h"
  18. #include "ui/painter.h"
  19. #include "ui/rect.h"
  20. #include "ui/widgets/menu/menu_item_base.h"
  21. #include "ui/widgets/popup_menu.h"
  22. #include "window/window_controller.h"
  23. #include "window/window_session_controller.h"
  24. #include "styles/style_calls.h"
  25. #include "styles/style_chat.h" // popupMenuExpandedSeparator.
  26. #include "styles/style_menu_icons.h"
  27. namespace {
  28. [[nodiscard]] QString Trim(QString text) {
  29. return text
  30. .replace('+', QString())
  31. .replace(' ', QString())
  32. .replace('-', QString());
  33. }
  34. class ResolvePhoneAction final : public Ui::Menu::ItemBase {
  35. public:
  36. ResolvePhoneAction(
  37. not_null<Ui::RpWidget*> parent,
  38. const style::Menu &st,
  39. const QString &phone,
  40. not_null<Window::SessionController*> controller);
  41. bool isEnabled() const override;
  42. not_null<QAction*> action() const override;
  43. void handleKeyPress(not_null<QKeyEvent*> e) override;
  44. [[nodiscard]] QString firstName() const;
  45. [[nodiscard]] QString lastName() const;
  46. protected:
  47. QPoint prepareRippleStartPosition() const override;
  48. QImage prepareRippleMask() const override;
  49. int contentHeight() const override;
  50. private:
  51. void prepare();
  52. void paint(Painter &p);
  53. const not_null<QAction*> _dummyAction;
  54. const style::Menu &_st;
  55. rpl::variable<PeerData*> _peer;
  56. rpl::variable<bool> _loaded;
  57. Ui::PeerUserpicView _userpicView;
  58. MTP::Sender _api;
  59. Ui::Text::String _above;
  60. Ui::Text::String _below;
  61. int _aboveWidth = 0;
  62. int _belowWidth = 0;
  63. const int _height = 0;
  64. };
  65. ResolvePhoneAction::ResolvePhoneAction(
  66. not_null<Ui::RpWidget*> parent,
  67. const style::Menu &st,
  68. const QString &phone,
  69. not_null<Window::SessionController*> controller)
  70. : ItemBase(parent, st)
  71. , _dummyAction(new QAction(parent))
  72. , _st(st)
  73. , _api(&controller->session().mtp())
  74. , _height(rect::m::sum::v(st::groupCallJoinAsPadding)
  75. + st::groupCallJoinAsPhotoSize) {
  76. setAcceptBoth(true);
  77. initResizeHook(parent->sizeValue());
  78. setClickedCallback([=] {
  79. if (const auto peer = _peer.current()) {
  80. controller->showPeerInfo(peer);
  81. }
  82. });
  83. const auto formattedPhone = Trim(phone);
  84. const auto owner = &controller->session().data();
  85. if (const auto peer = owner->userByPhone(formattedPhone)) {
  86. _peer = peer;
  87. _loaded.force_assign(true);
  88. } else {
  89. _api.request(MTPcontacts_ResolvePhone(
  90. MTP_string(phone)
  91. )).done([=](const MTPcontacts_ResolvedPeer &result) {
  92. result.match([&](const MTPDcontacts_resolvedPeer &data) {
  93. owner->processUsers(data.vusers());
  94. owner->processChats(data.vchats());
  95. if (const auto peerId = peerFromMTP(data.vpeer())) {
  96. _peer = owner->peer(peerId);
  97. }
  98. _loaded.force_assign(true);
  99. });
  100. }).fail([=](const MTP::Error &error) {
  101. if (error.code() == 400) {
  102. _peer.force_assign(nullptr);
  103. _loaded.force_assign(true);
  104. }
  105. }).send();
  106. }
  107. paintRequest(
  108. ) | rpl::start_with_next([=] {
  109. Painter p(this);
  110. paint(p);
  111. }, lifetime());
  112. enableMouseSelecting();
  113. prepare();
  114. }
  115. QString ResolvePhoneAction::firstName() const {
  116. const auto peer = _peer.current();
  117. const auto user = peer ? peer->asUser() : nullptr;
  118. return user ? user->firstName : QString();
  119. }
  120. QString ResolvePhoneAction::lastName() const {
  121. const auto peer = _peer.current();
  122. const auto user = peer ? peer->asUser() : nullptr;
  123. return user ? user->lastName : QString();
  124. }
  125. void ResolvePhoneAction::paint(Painter &p) {
  126. const auto selected = isSelected() && _peer.current();
  127. const auto height = contentHeight();
  128. if (selected && _st.itemBgOver->c.alpha() < 255) {
  129. p.fillRect(0, 0, width(), height, _st.itemBg);
  130. }
  131. p.fillRect(0, 0, width(), height, selected ? _st.itemBgOver : _st.itemBg);
  132. if (isEnabled()) {
  133. paintRipple(p, 0, 0);
  134. }
  135. const auto &padding = st::groupCallJoinAsPadding;
  136. const auto textLeft = padding.left()
  137. + st::groupCallJoinAsPhotoSize
  138. + padding.left();
  139. if (const auto peer = _peer.current()) {
  140. peer->paintUserpic(
  141. p,
  142. _userpicView,
  143. padding.left(),
  144. padding.top(),
  145. st::groupCallJoinAsPhotoSize);
  146. p.setPen(selected ? _st.itemFgOver : _st.itemFg);
  147. _above.drawLeftElided(
  148. p,
  149. textLeft,
  150. st::groupCallJoinAsTextTop,
  151. width() - textLeft - padding.right(),
  152. width());
  153. p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
  154. _below.drawLeftElided(
  155. p,
  156. textLeft,
  157. st::groupCallJoinAsNameTop,
  158. _belowWidth,
  159. width());
  160. } else {
  161. p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
  162. const auto w = width() - padding.left() - padding.right();
  163. _below.draw(p, Ui::Text::PaintContext{
  164. .position = QPoint(
  165. (width() - w) / 2,
  166. (height - _below.countHeight(w)) / 2),
  167. .outerWidth = w,
  168. .availableWidth = w,
  169. .align = style::al_center,
  170. .elisionLines = 2,
  171. });
  172. }
  173. }
  174. void ResolvePhoneAction::prepare() {
  175. rpl::combine(
  176. tr::lng_context_view_profile(),
  177. _peer.value(
  178. ) | rpl::map([](PeerData *peer) {
  179. return peer
  180. ? Info::Profile::NameValue(peer)
  181. : rpl::single(QString());
  182. }) | rpl::flatten_latest(),
  183. tr::lng_menu_not_contact(),
  184. _loaded.value(
  185. ) | rpl::map([](bool loaded) {
  186. return loaded
  187. ? rpl::single(QString())
  188. : tr::lng_contacts_loading();
  189. }) | rpl::flatten_latest()
  190. ) | rpl::start_with_next([=](
  191. QString text,
  192. QString name,
  193. QString no,
  194. QString loading) {
  195. const auto &padding = st::groupCallJoinAsPadding;
  196. QWidget::setAttribute(
  197. Qt::WA_TransparentForMouseEvents,
  198. !_peer.current());
  199. const auto above = name;
  200. const auto below = !loading.isEmpty()
  201. ? loading
  202. : name.isEmpty()
  203. ? no
  204. : text;
  205. const auto options = kDefaultTextOptions;
  206. const auto tempWidth = [&] {
  207. _below.setMarkedText(_st.itemStyle, { text }, options);
  208. return _below.maxWidth();
  209. }();
  210. const auto textLeft = padding.left()
  211. + st::groupCallJoinAsPhotoSize
  212. + padding.left();
  213. const auto w = std::clamp(
  214. (textLeft + tempWidth + padding.right()),
  215. _st.widthMin,
  216. _st.widthMax);
  217. if (!no.isEmpty()) {
  218. _below = Ui::Text::String(w);
  219. }
  220. _above.setMarkedText(_st.itemStyle, { above }, options);
  221. _below.setMarkedText(_st.itemStyle, { below }, options);
  222. setMinWidth(w);
  223. _aboveWidth = w - textLeft - padding.right();
  224. _belowWidth = w
  225. - ((loading.isEmpty() && name.isEmpty()) ? 0 : textLeft)
  226. - padding.right();
  227. update();
  228. }, lifetime());
  229. }
  230. bool ResolvePhoneAction::isEnabled() const {
  231. return true;
  232. }
  233. not_null<QAction*> ResolvePhoneAction::action() const {
  234. return _dummyAction;
  235. }
  236. QPoint ResolvePhoneAction::prepareRippleStartPosition() const {
  237. return mapFromGlobal(QCursor::pos());
  238. }
  239. QImage ResolvePhoneAction::prepareRippleMask() const {
  240. return Ui::RippleAnimation::RectMask(size());
  241. }
  242. int ResolvePhoneAction::contentHeight() const {
  243. return _height;
  244. }
  245. void ResolvePhoneAction::handleKeyPress(not_null<QKeyEvent*> e) {
  246. if (!isSelected() || !_peer.current()) {
  247. return;
  248. }
  249. const auto key = e->key();
  250. if (key == Qt::Key_Enter || key == Qt::Key_Return) {
  251. setClicked(Ui::Menu::TriggeredSource::Keyboard);
  252. }
  253. }
  254. } // namespace
  255. PhoneClickHandler::PhoneClickHandler(
  256. not_null<Main::Session*> session,
  257. QString text)
  258. : _session(session)
  259. , _text(text) {
  260. setProperty(kPhoneNumberLinkProperty, _text);
  261. }
  262. void PhoneClickHandler::onClick(ClickContext context) const {
  263. if (context.button != Qt::LeftButton) {
  264. return;
  265. }
  266. const auto my = context.other.value<ClickHandlerContext>();
  267. const auto controller = my.sessionWindow.get();
  268. const auto pos = QCursor::pos();
  269. if (!controller) {
  270. return;
  271. }
  272. const auto menu = Ui::CreateChild<Ui::PopupMenu>(
  273. controller->content(),
  274. st::popupMenuWithIcons);
  275. const auto phone = _text;
  276. #if 0
  277. const auto maybeContact = [&]() -> PeerData* {
  278. const auto &chats = controller->session().data().contactsList();
  279. for (const auto &row : chats->all()) {
  280. if (const auto history = row->history()) {
  281. if (const auto user = history->peer->asUser()) {
  282. if (Trim(user->phone()) == Trim(phone)) {
  283. return user;
  284. }
  285. }
  286. }
  287. }
  288. return nullptr;
  289. }();
  290. #endif
  291. menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
  292. TextUtilities::SetClipboardText(
  293. TextForMimeData::Simple(phone.trimmed()));
  294. }, &st::menuIconCopy);
  295. auto resolvePhoneAction = base::make_unique_q<ResolvePhoneAction>(
  296. menu,
  297. menu->st().menu,
  298. phone,
  299. controller);
  300. if (Trim(phone) != Trim(controller->session().user()->phone())) {
  301. menu->addAction(
  302. tr::lng_info_add_as_contact(tr::now),
  303. [=, raw = Ui::MakeWeak(resolvePhoneAction.get())] {
  304. controller->show(
  305. Box<AddContactBox>(
  306. &controller->session(),
  307. raw ? raw->firstName() : QString(),
  308. raw ? raw->lastName() : QString(),
  309. Trim(phone)));
  310. },
  311. &st::menuIconInvite);
  312. }
  313. menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator);
  314. menu->addAction(std::move(resolvePhoneAction));
  315. menu->popup(pos);
  316. }
  317. auto PhoneClickHandler::getTextEntity() const -> TextEntity {
  318. return { EntityType::Phone };
  319. }
  320. QString PhoneClickHandler::tooltip() const {
  321. return _text;
  322. }