Added initial api support of bank card entity in messages.

This commit is contained in:
23rd 2025-05-01 13:48:55 +03:00
parent 75b33c906d
commit 0fe9dad515
9 changed files with 305 additions and 1 deletions

View file

@ -446,6 +446,8 @@ PRIVATE
chat_helpers/ttl_media_layer_widget.h
core/application.cpp
core/application.h
core/bank_card_click_handler.cpp
core/bank_card_click_handler.h
core/base_integration.cpp
core/base_integration.h
core/changelogs.cpp

View file

@ -6440,6 +6440,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_frozen_text3" = "Appeal via {link} before {date}, or your account will be deleted.";
"lng_frozen_appeal_button" = "Submit an Appeal";
"lng_context_bank_card_copy" = "Copy Card Number";
"lng_context_bank_card_copied" = "Card number copied to clipboard.";
// Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

@ -202,7 +202,11 @@ EntitiesInText EntitiesFromMTP(
d.vlength().v,
});
}, [&](const MTPDmessageEntityBankCard &d) {
// Skipping cards. // #TODO entities
result.push_back({
EntityType::BankCard,
d.voffset().v,
d.vlength().v,
});
}, [&](const MTPDmessageEntitySpoiler &d) {
result.push_back({
EntityType::Spoiler,
@ -273,6 +277,9 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(
case EntityType::Phone: {
v.push_back(MTP_messageEntityPhone(offset, length));
} break;
case EntityType::BankCard: {
v.push_back(MTP_messageEntityBankCard(offset, length));
} break;
case EntityType::Hashtag: {
v.push_back(MTP_messageEntityHashtag(offset, length));
} break;

View file

@ -0,0 +1,254 @@
/*
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/bank_card_click_handler.h"
#include "core/click_handler_types.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "mainwidget.h"
#include "mtproto/sender.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/widgets/menu/menu_multiline_action.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 {
struct State final {
State(not_null<Main::Session*> session) : sender(&session->mtp()) {
}
MTP::Sender sender;
};
struct BankCardData final {
QString title;
std::vector<EntityLinkData> links;
};
enum class Status {
Loading,
Resolved,
Failed,
};
void RequestResolveBankCard(
not_null<State*> state,
const QString &bankCard,
Fn<void(BankCardData)> done,
Fn<void(QString)> fail) {
state->sender.request(MTPpayments_GetBankCardData(
MTP_string(bankCard)
)).done([=](const MTPpayments_BankCardData &result) {
auto bankCardData = BankCardData{
.title = qs(result.data().vtitle()),
};
for (const auto &tl : result.data().vopen_urls().v) {
const auto url = qs(tl.data().vurl());
const auto name = qs(tl.data().vname());
bankCardData.links.emplace_back(EntityLinkData{
.text = name,
.data = url,
});
}
done(std::move(bankCardData));
}).fail([=](const MTP::Error &error) {
fail(error.type());
}).send();
}
class ResolveBankCardAction final : public Ui::Menu::ItemBase {
public:
ResolveBankCardAction(
not_null<Ui::RpWidget*> parent,
const style::Menu &st);
void setStatus(Status status);
bool isEnabled() const override;
not_null<QAction*> action() const override;
protected:
int contentHeight() const override;
void paintEvent(QPaintEvent *e) override;
private:
void paint(Painter &p);
const not_null<QAction*> _dummyAction;
const style::Menu &_st;
const int _height = 0;
Status _status = Status::Loading;
Ui::Text::String _text;
};
ResolveBankCardAction::ResolveBankCardAction(
not_null<Ui::RpWidget*> parent,
const style::Menu &st)
: ItemBase(parent, st)
, _dummyAction(Ui::CreateChild<QAction>(parent))
, _st(st)
, _height(st::groupCallJoinAsPhotoSize) {
setAcceptBoth(true);
initResizeHook(parent->sizeValue());
setStatus(Status::Loading);
}
void ResolveBankCardAction::setStatus(Status status) {
_status = status;
if (status == Status::Resolved) {
resize(width(), 0);
} else if (status == Status::Failed) {
_text.setText(_st.itemStyle, tr::lng_attach_failed(tr::now));
} else if (status == Status::Loading) {
_text.setText(_st.itemStyle, tr::lng_contacts_loading(tr::now));
}
update();
}
void ResolveBankCardAction::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
const auto selected = false;
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);
const auto &padding = st::groupCallJoinAsPadding;
const auto textLeft = padding.left()
+ st::groupCallJoinAsPhotoSize
+ padding.left();
{
p.setPen(selected ? _st.itemFgShortcutOver : _st.itemFgShortcut);
const auto w = width() - padding.left() - padding.right();
_text.draw(p, Ui::Text::PaintContext{
.position = QPoint(
(width() - w) / 2,
(height - _text.countHeight(w)) / 2),
.outerWidth = w,
.availableWidth = w,
.align = style::al_center,
.elisionLines = 2,
});
}
}
bool ResolveBankCardAction::isEnabled() const {
return false;
}
not_null<QAction*> ResolveBankCardAction::action() const {
return _dummyAction;
}
int ResolveBankCardAction::contentHeight() const {
if (_status == Status::Resolved) {
return 0;
}
return _height;
}
} // namespace
BankCardClickHandler::BankCardClickHandler(
not_null<Main::Session*> session,
QString text)
: _session(session)
, _text(text) {
}
void BankCardClickHandler::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 bankCard = _text;
const auto copy = [bankCard, show = controller->uiShow()] {
TextUtilities::SetClipboardText(
TextForMimeData::Simple(bankCard));
show->showToast(tr::lng_context_bank_card_copied(tr::now));
};
menu->addAction(
tr::lng_context_bank_card_copy(tr::now),
copy,
&st::menuIconCopy);
auto resolveBankCardAction = base::make_unique_q<ResolveBankCardAction>(
menu,
menu->st().menu);
const auto resolveBankCardRaw = resolveBankCardAction.get();
menu->addSeparator(&st::popupMenuExpandedSeparator.menu.separator);
menu->addAction(std::move(resolveBankCardAction));
const auto addTitle = [=](const QString &name) {
auto button = base::make_unique_q<Ui::Menu::MultilineAction>(
menu,
menu->st().menu,
st::historyHasCustomEmoji,
st::historyBankCardMenuMultilinePosition,
TextWithEntities{ name });
button->setClickedCallback(copy);
menu->addAction(std::move(button));
};
const auto state = menu->lifetime().make_state<State>(
&controller->session());
RequestResolveBankCard(
state,
bankCard,
[=](BankCardData data) {
resolveBankCardRaw->setStatus(Status::Resolved);
for (auto &link : data.links) {
menu->addAction(
base::take(link.text),
[u = base::take(link.data)] { UrlClickHandler::Open(u); },
&st::menuIconPayment);
}
if (!data.title.isEmpty()) {
addTitle(base::take(data.title));
}
},
[=](const QString &) {
resolveBankCardRaw->setStatus(Status::Failed);
});
menu->popup(pos);
}
auto BankCardClickHandler::getTextEntity() const -> TextEntity {
return { EntityType::BankCard };
}
QString BankCardClickHandler::tooltip() const {
return _text;
}

View 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 BankCardClickHandler : public ClickHandler {
public:
BankCardClickHandler(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;
};

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/local_url_handlers.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "core/bank_card_click_handler.h"
#include "core/sandbox.h"
#include "core/click_handler_types.h"
#include "data/stickers/data_custom_emoji.h"
@ -256,6 +257,10 @@ std::shared_ptr<ClickHandler> UiIntegration::createLinkHandler(
return my->session
? std::make_shared<PhoneClickHandler>(my->session, data.text)
: nullptr;
case EntityType::BankCard:
return my->session
? std::make_shared<BankCardClickHandler>(my->session, data.text)
: nullptr;
}
return Integration::createLinkHandler(data, context);
}

View file

@ -3464,6 +3464,7 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) {
if (type == EntityType::Url
|| type == EntityType::CustomUrl
|| type == EntityType::Phone
|| type == EntityType::BankCard
|| type == EntityType::Email) {
_flags |= MessageFlag::HasTextLinks;
break;

View file

@ -949,6 +949,7 @@ historyHasCustomEmoji: FlatLabel(defaultFlatLabel) {
minWidth: 80px;
}
historyHasCustomEmojiPosition: point(12px, 4px);
historyBankCardMenuMultilinePosition: point(18px, 4px);
historyTranslateLabel: FlatLabel(defaultFlatLabel) {
style: semiboldTextStyle;

View file

@ -178,6 +178,7 @@ menuIconNftWear: icon {{ "menu/nft_wear", menuIconColor }};
menuIconNftTakeOff: icon {{ "menu/nft_takeoff", menuIconColor }};
menuIconShortcut: icon {{ "menu/shortcut", menuIconColor }};
menuIconHourglass: icon {{ "menu/hourglass", menuIconColor }};
menuIconPayment: icon {{ "payments/payment_card", menuIconColor }};
menuIconTTLAny: icon {{ "menu/auto_delete_plain", menuIconColor }};
menuIconTTLAnyTextPosition: point(11px, 22px);