mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Added initial api support of text phone entity in messages.
This commit is contained in:
parent
50ce847b31
commit
ba611d0f2d
9 changed files with 373 additions and 2 deletions
|
@ -445,6 +445,8 @@ PRIVATE
|
||||||
core/launcher.h
|
core/launcher.h
|
||||||
core/local_url_handlers.cpp
|
core/local_url_handlers.cpp
|
||||||
core/local_url_handlers.h
|
core/local_url_handlers.h
|
||||||
|
core/phone_click_handler.cpp
|
||||||
|
core/phone_click_handler.h
|
||||||
core/sandbox.cpp
|
core/sandbox.cpp
|
||||||
core/sandbox.h
|
core/sandbox.h
|
||||||
core/shortcuts.cpp
|
core/shortcuts.cpp
|
||||||
|
|
|
@ -3427,6 +3427,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_add_contact" = "Create";
|
"lng_add_contact" = "Create";
|
||||||
"lng_add_contact_button" = "New contact";
|
"lng_add_contact_button" = "New contact";
|
||||||
"lng_contacts_header" = "Contacts";
|
"lng_contacts_header" = "Contacts";
|
||||||
|
"lng_menu_not_contact" = "This number is not on Telegram";
|
||||||
"lng_contacts_hidden_stories" = "Hidden Stories";
|
"lng_contacts_hidden_stories" = "Hidden Stories";
|
||||||
"lng_contacts_stories_status#one" = "{count} story";
|
"lng_contacts_stories_status#one" = "{count} story";
|
||||||
"lng_contacts_stories_status#other" = "{count} stories";
|
"lng_contacts_stories_status#other" = "{count} stories";
|
||||||
|
|
|
@ -178,7 +178,11 @@ EntitiesInText EntitiesFromMTP(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [&](const MTPDmessageEntityPhone &d) {
|
}, [&](const MTPDmessageEntityPhone &d) {
|
||||||
// Skipping phones.
|
result.push_back({
|
||||||
|
EntityType::Phone,
|
||||||
|
d.voffset().v,
|
||||||
|
d.vlength().v,
|
||||||
|
});
|
||||||
}, [&](const MTPDmessageEntityCashtag &d) {
|
}, [&](const MTPDmessageEntityCashtag &d) {
|
||||||
result.push_back({
|
result.push_back({
|
||||||
EntityType::Cashtag,
|
EntityType::Cashtag,
|
||||||
|
@ -266,6 +270,9 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
|
||||||
case EntityType::Email: {
|
case EntityType::Email: {
|
||||||
v.push_back(MTP_messageEntityEmail(offset, length));
|
v.push_back(MTP_messageEntityEmail(offset, length));
|
||||||
} break;
|
} break;
|
||||||
|
case EntityType::Phone: {
|
||||||
|
v.push_back(MTP_messageEntityPhone(offset, length));
|
||||||
|
} break;
|
||||||
case EntityType::Hashtag: {
|
case EntityType::Hashtag: {
|
||||||
v.push_back(MTP_messageEntityHashtag(offset, length));
|
v.push_back(MTP_messageEntityHashtag(offset, length));
|
||||||
} break;
|
} break;
|
||||||
|
|
|
@ -52,6 +52,8 @@ struct ClickHandlerContext {
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(ClickHandlerContext);
|
Q_DECLARE_METATYPE(ClickHandlerContext);
|
||||||
|
|
||||||
|
class PhoneClickHandler;
|
||||||
|
|
||||||
class HiddenUrlClickHandler : public UrlClickHandler {
|
class HiddenUrlClickHandler : public UrlClickHandler {
|
||||||
public:
|
public:
|
||||||
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
|
HiddenUrlClickHandler(QString url) : UrlClickHandler(url, false) {
|
||||||
|
|
325
Telegram/SourceFiles/core/phone_click_handler.cpp
Normal file
325
Telegram/SourceFiles/core/phone_click_handler.cpp
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
/*
|
||||||
|
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 "core/phone_click_handler.h"
|
||||||
|
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "info/profile/info_profile_values.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "mainwidget.h"
|
||||||
|
#include "mtproto/sender.h"
|
||||||
|
#include "ui/effects/ripple_animation.h"
|
||||||
|
#include "ui/painter.h"
|
||||||
|
#include "ui/rect.h"
|
||||||
|
#include "ui/widgets/menu/menu_item_base.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "window/window_controller.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_calls.h"
|
||||||
|
#include "styles/style_chat.h" // popupMenuExpandedSeparator.
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] QString Trim(QString text) {
|
||||||
|
return text
|
||||||
|
.replace('+', QString())
|
||||||
|
.replace(' ', QString())
|
||||||
|
.replace('-', QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResolvePhoneAction final : public Ui::Menu::ItemBase {
|
||||||
|
public:
|
||||||
|
ResolvePhoneAction(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
const style::Menu &st,
|
||||||
|
const QString &phone,
|
||||||
|
not_null<Window::SessionController*> controller);
|
||||||
|
|
||||||
|
bool isEnabled() const override;
|
||||||
|
not_null<QAction*> action() const override;
|
||||||
|
|
||||||
|
void handleKeyPress(not_null<QKeyEvent*> e) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QPoint prepareRippleStartPosition() const override;
|
||||||
|
QImage prepareRippleMask() const override;
|
||||||
|
|
||||||
|
int contentHeight() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void prepare();
|
||||||
|
void paint(Painter &p);
|
||||||
|
|
||||||
|
const not_null<QAction*> _dummyAction;
|
||||||
|
const style::Menu &_st;
|
||||||
|
rpl::variable<PeerData*> _peer;
|
||||||
|
rpl::variable<bool> _loaded;
|
||||||
|
Ui::PeerUserpicView _userpicView;
|
||||||
|
|
||||||
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
Ui::Text::String _above;
|
||||||
|
Ui::Text::String _below;
|
||||||
|
int _aboveWidth = 0;
|
||||||
|
int _belowWidth = 0;
|
||||||
|
const int _height = 0;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ResolvePhoneAction::ResolvePhoneAction(
|
||||||
|
not_null<Ui::RpWidget*> parent,
|
||||||
|
const style::Menu &st,
|
||||||
|
const QString &phone,
|
||||||
|
not_null<Window::SessionController*> controller)
|
||||||
|
: ItemBase(parent, st)
|
||||||
|
, _dummyAction(new QAction(parent))
|
||||||
|
, _st(st)
|
||||||
|
, _api(&controller->session().mtp())
|
||||||
|
, _height(rect::m::sum::v(st::groupCallJoinAsPadding)
|
||||||
|
+ st::groupCallJoinAsPhotoSize) {
|
||||||
|
setAcceptBoth(true);
|
||||||
|
initResizeHook(parent->sizeValue());
|
||||||
|
setClickedCallback([=] {
|
||||||
|
if (const auto peer = _peer.current()) {
|
||||||
|
controller->showPeerInfo(peer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto formattedPhone = Trim(phone);
|
||||||
|
|
||||||
|
const auto owner = &controller->session().data();
|
||||||
|
|
||||||
|
if (const auto peer = owner->userByPhone(formattedPhone)) {
|
||||||
|
_peer = peer;
|
||||||
|
_loaded.force_assign(true);
|
||||||
|
} else {
|
||||||
|
_api.request(MTPcontacts_ResolvePhone(
|
||||||
|
MTP_string(phone)
|
||||||
|
)).done([=](const MTPcontacts_ResolvedPeer &result) {
|
||||||
|
result.match([&](const MTPDcontacts_resolvedPeer &data) {
|
||||||
|
owner->processUsers(data.vusers());
|
||||||
|
owner->processChats(data.vchats());
|
||||||
|
if (const auto peerId = peerFromMTP(data.vpeer())) {
|
||||||
|
_peer = owner->peer(peerId);
|
||||||
|
}
|
||||||
|
_loaded.force_assign(true);
|
||||||
|
});
|
||||||
|
}).fail([=](const MTP::Error &error) {
|
||||||
|
if (error.code() == 400) {
|
||||||
|
_peer.force_assign(nullptr);
|
||||||
|
_loaded.force_assign(true);
|
||||||
|
}
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
paintRequest(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
Painter p(this);
|
||||||
|
paint(p);
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
enableMouseSelecting();
|
||||||
|
prepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolvePhoneAction::paint(Painter &p) {
|
||||||
|
const auto selected = isSelected() && _peer.current();
|
||||||
|
const auto height = contentHeight();
|
||||||
|
if (selected && _st.itemBgOver->c.alpha() < 255) {
|
||||||
|
p.fillRect(0, 0, width(), height, _st.itemBg);
|
||||||
|
}
|
||||||
|
p.fillRect(0, 0, width(), height, selected ? _st.itemBgOver : _st.itemBg);
|
||||||
|
if (isEnabled()) {
|
||||||
|
paintRipple(p, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &padding = st::groupCallJoinAsPadding;
|
||||||
|
const auto textLeft = padding.left()
|
||||||
|
+ st::groupCallJoinAsPhotoSize
|
||||||
|
+ padding.left();
|
||||||
|
if (const auto peer = _peer.current()) {
|
||||||
|
peer->paintUserpic(
|
||||||
|
p,
|
||||||
|
_userpicView,
|
||||||
|
padding.left(),
|
||||||
|
padding.top(),
|
||||||
|
st::groupCallJoinAsPhotoSize);
|
||||||
|
p.setPen(selected ? _st.itemFgOver : _st.itemFg);
|
||||||
|
_above.drawLeftElided(
|
||||||
|
p,
|
||||||
|
textLeft,
|
||||||
|
st::groupCallJoinAsTextTop,
|
||||||
|
width() - textLeft - padding.right(),
|
||||||
|
width());
|
||||||
|
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
|
||||||
|
_below.drawLeftElided(
|
||||||
|
p,
|
||||||
|
textLeft,
|
||||||
|
st::groupCallJoinAsNameTop,
|
||||||
|
_belowWidth,
|
||||||
|
width());
|
||||||
|
} else {
|
||||||
|
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
|
||||||
|
p.drawText(rect() - padding, _below.toString(), style::al_center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolvePhoneAction::prepare() {
|
||||||
|
rpl::combine(
|
||||||
|
tr::lng_context_view_profile(),
|
||||||
|
_peer.value(
|
||||||
|
) | rpl::map([](PeerData *peer) {
|
||||||
|
return peer
|
||||||
|
? Info::Profile::NameValue(peer)
|
||||||
|
: rpl::single(QString());
|
||||||
|
}) | rpl::flatten_latest(),
|
||||||
|
tr::lng_menu_not_contact(),
|
||||||
|
_loaded.value(
|
||||||
|
) | rpl::map([](bool loaded) {
|
||||||
|
return loaded
|
||||||
|
? rpl::single(QString())
|
||||||
|
: tr::lng_contacts_loading();
|
||||||
|
}) | rpl::flatten_latest()
|
||||||
|
) | rpl::start_with_next([=](
|
||||||
|
QString text,
|
||||||
|
QString name,
|
||||||
|
QString no,
|
||||||
|
QString loading) {
|
||||||
|
const auto &padding = st::groupCallJoinAsPadding;
|
||||||
|
QWidget::setAttribute(
|
||||||
|
Qt::WA_TransparentForMouseEvents,
|
||||||
|
!_peer.current());
|
||||||
|
const auto above = name;
|
||||||
|
const auto below = !loading.isEmpty()
|
||||||
|
? loading
|
||||||
|
: name.isEmpty()
|
||||||
|
? no
|
||||||
|
: text;
|
||||||
|
const auto options = kDefaultTextOptions;
|
||||||
|
const auto tempWidth = [&] {
|
||||||
|
_below.setMarkedText(_st.itemStyle, { text }, options);
|
||||||
|
return _below.maxWidth();
|
||||||
|
}();
|
||||||
|
_above.setMarkedText(_st.itemStyle, { above }, options);
|
||||||
|
_below.setMarkedText(_st.itemStyle, { below }, options);
|
||||||
|
const auto textWidth = _above.maxWidth();
|
||||||
|
const auto nameWidth = _below.maxWidth();
|
||||||
|
const auto textLeft = padding.left()
|
||||||
|
+ st::groupCallJoinAsPhotoSize
|
||||||
|
+ padding.left();
|
||||||
|
const auto w = std::clamp(
|
||||||
|
(textLeft + tempWidth + padding.right()),
|
||||||
|
_st.widthMin,
|
||||||
|
_st.widthMax);
|
||||||
|
setMinWidth(w);
|
||||||
|
_aboveWidth = w - textLeft - padding.right();
|
||||||
|
_belowWidth = w
|
||||||
|
- ((loading.isEmpty() && name.isEmpty()) ? 0 : textLeft)
|
||||||
|
- padding.right();
|
||||||
|
update();
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ResolvePhoneAction::isEnabled() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_null<QAction*> ResolvePhoneAction::action() const {
|
||||||
|
return _dummyAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint ResolvePhoneAction::prepareRippleStartPosition() const {
|
||||||
|
return mapFromGlobal(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage ResolvePhoneAction::prepareRippleMask() const {
|
||||||
|
return Ui::RippleAnimation::RectMask(size());
|
||||||
|
}
|
||||||
|
|
||||||
|
int ResolvePhoneAction::contentHeight() const {
|
||||||
|
return _height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResolvePhoneAction::handleKeyPress(not_null<QKeyEvent*> e) {
|
||||||
|
if (!isSelected() || !_peer.current()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto key = e->key();
|
||||||
|
if (key == Qt::Key_Enter || key == Qt::Key_Return) {
|
||||||
|
setClicked(Ui::Menu::TriggeredSource::Keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PhoneClickHandler::PhoneClickHandler(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
QString text)
|
||||||
|
: _session(session)
|
||||||
|
, _text(text) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneClickHandler::onClick(ClickContext context) const {
|
||||||
|
if (context.button != Qt::LeftButton) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
|
const auto controller = my.sessionWindow.get();
|
||||||
|
const auto pos = QCursor::pos();
|
||||||
|
if (!controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto menu = Ui::CreateChild<Ui::PopupMenu>(
|
||||||
|
controller->content(),
|
||||||
|
st::popupMenuWithIcons);
|
||||||
|
|
||||||
|
const auto phone = _text;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
const auto maybeContact = [&]() -> PeerData* {
|
||||||
|
const auto &chats = controller->session().data().contactsList();
|
||||||
|
for (const auto &row : chats->all()) {
|
||||||
|
if (const auto history = row->history()) {
|
||||||
|
if (const auto user = history->peer->asUser()) {
|
||||||
|
if (Trim(user->phone()) == Trim(phone)) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
menu->addAction(tr::lng_profile_copy_phone(tr::now), [=] {
|
||||||
|
TextUtilities::SetClipboardText(
|
||||||
|
TextForMimeData::Simple(phone.trimmed()));
|
||||||
|
}, &st::menuIconCopy);
|
||||||
|
|
||||||
|
menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator);
|
||||||
|
|
||||||
|
menu->addAction(
|
||||||
|
base::make_unique_q<ResolvePhoneAction>(
|
||||||
|
menu,
|
||||||
|
menu->st().menu,
|
||||||
|
phone,
|
||||||
|
controller));
|
||||||
|
|
||||||
|
menu->popup(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PhoneClickHandler::getTextEntity() const -> TextEntity {
|
||||||
|
return { EntityType::Phone };
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PhoneClickHandler::tooltip() const {
|
||||||
|
return _text;
|
||||||
|
}
|
30
Telegram/SourceFiles/core/phone_click_handler.h
Normal file
30
Telegram/SourceFiles/core/phone_click_handler.h
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/basic_click_handlers.h"
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
class PhoneClickHandler : public ClickHandler {
|
||||||
|
public:
|
||||||
|
PhoneClickHandler(not_null<Main::Session*> session, QString text);
|
||||||
|
|
||||||
|
void onClick(ClickContext context) const override;
|
||||||
|
|
||||||
|
TextEntity getTextEntity() const override;
|
||||||
|
|
||||||
|
QString tooltip() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
QString _text;
|
||||||
|
|
||||||
|
};
|
|
@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "platform/platform_specific.h"
|
#include "platform/platform_specific.h"
|
||||||
#include "boxes/url_auth_box.h"
|
#include "boxes/url_auth_box.h"
|
||||||
|
#include "core/phone_click_handler.h"
|
||||||
#include "main/main_account.h"
|
#include "main/main_account.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_app_config.h"
|
#include "main/main_app_config.h"
|
||||||
|
@ -217,6 +218,8 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
|
||||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||||
case EntityType::Pre:
|
case EntityType::Pre:
|
||||||
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
return std::make_shared<MonospaceClickHandler>(data.text, data.type);
|
||||||
|
case EntityType::Phone:
|
||||||
|
return std::make_shared<PhoneClickHandler>(my->session, data.text);
|
||||||
}
|
}
|
||||||
return Integration::createLinkHandler(data, context);
|
return Integration::createLinkHandler(data, context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3127,6 +3127,7 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) {
|
||||||
auto type = entity.type();
|
auto type = entity.type();
|
||||||
if (type == EntityType::Url
|
if (type == EntityType::Url
|
||||||
|| type == EntityType::CustomUrl
|
|| type == EntityType::CustomUrl
|
||||||
|
|| type == EntityType::Phone
|
||||||
|| type == EntityType::Email) {
|
|| type == EntityType::Email) {
|
||||||
_flags |= MessageFlag::HasTextLinks;
|
_flags |= MessageFlag::HasTextLinks;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit d0514b2b022043b3777b06d6068232aa4cda7e80
|
Subproject commit 4440037244bd0175752b82ee1177c676a5340f5c
|
Loading…
Add table
Reference in a new issue