Added initial support of vcard from media contacts.

This commit is contained in:
23rd 2024-06-02 22:06:12 +03:00
parent 1656a9c3e2
commit 47ce34e987
6 changed files with 216 additions and 37 deletions

View file

@ -5240,6 +5240,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_search_tab_no_results_retry" = "Try another hashtag."; "lng_search_tab_no_results_retry" = "Try another hashtag.";
"lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it."; "lng_search_tab_by_hashtag" = "Enter a hashtag to find messages containing it.";
"lng_contact_details_button" = "View Contact";
"lng_contact_details_title" = "Contact details";
"lng_contact_details_phone" = "Phone";
"lng_contact_details_phone_main" = "Main Phone";
"lng_contact_details_phone_home" = "Home Phone";
"lng_contact_details_phone_mobile" = "Mobile Phone";
"lng_contact_details_phone_work" = "Work Phone";
"lng_contact_details_phone_other" = "Other Phone";
"lng_contact_details_email" = "Email";
"lng_contact_details_address" = "Address";
"lng_contact_details_url" = "URL";
"lng_contact_details_note" = "Note";
"lng_contact_details_birthday" = "Birthday";
"lng_contact_details_organization" = "Organization";
// Wnd specific // Wnd specific
"lng_wnd_choose_program_menu" = "Choose Default Program..."; "lng_wnd_choose_program_menu" = "Choose Default Program...";

View file

@ -1224,14 +1224,17 @@ MediaContact::MediaContact(
UserId userId, UserId userId,
const QString &firstName, const QString &firstName,
const QString &lastName, const QString &lastName,
const QString &phoneNumber) const QString &phoneNumber,
: Media(parent) { const SharedContact::VcardItems &vcardItems)
: Media(parent)
, _contact(SharedContact{
.userId = userId,
.firstName = firstName,
.lastName = lastName,
.phoneNumber = phoneNumber,
.vcardItems = vcardItems,
}) {
parent->history()->owner().registerContactItem(userId, parent); parent->history()->owner().registerContactItem(userId, parent);
_contact.userId = userId;
_contact.firstName = firstName;
_contact.lastName = lastName;
_contact.phoneNumber = phoneNumber;
} }
MediaContact::~MediaContact() { MediaContact::~MediaContact() {
@ -1246,7 +1249,8 @@ std::unique_ptr<Media> MediaContact::clone(not_null<HistoryItem*> parent) {
_contact.userId, _contact.userId,
_contact.firstName, _contact.firstName,
_contact.lastName, _contact.lastName,
_contact.phoneNumber); _contact.phoneNumber,
_contact.vcardItems);
} }
const SharedContact *MediaContact::sharedContact() const { const SharedContact *MediaContact::sharedContact() const {
@ -1301,12 +1305,7 @@ std::unique_ptr<HistoryView::Media> MediaContact::createView(
not_null<HistoryView::Element*> message, not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent, not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) { HistoryView::Element *replacing) {
return std::make_unique<HistoryView::Contact>( return std::make_unique<HistoryView::Contact>(message, _contact);
message,
_contact.userId,
_contact.firstName,
_contact.lastName,
_contact.phoneNumber);
} }
MediaLocation::MediaLocation( MediaLocation::MediaLocation(

View file

@ -47,11 +47,30 @@ enum class CallFinishReason : char {
Hangup, Hangup,
}; };
struct SharedContact { struct SharedContact final {
UserId userId = 0; UserId userId = 0;
QString firstName; QString firstName;
QString lastName; QString lastName;
QString phoneNumber; QString phoneNumber;
enum class VcardItemType {
Phone,
PhoneMain,
PhoneHome,
PhoneMobile,
PhoneWork,
PhoneOther,
Email,
Address,
Url,
Note,
Birthday,
Organization,
Name,
};
using VcardItems = base::flat_map<VcardItemType, QString>;
VcardItems vcardItems;
}; };
struct Call { struct Call {
@ -308,7 +327,8 @@ public:
UserId userId, UserId userId,
const QString &firstName, const QString &firstName,
const QString &lastName, const QString &lastName,
const QString &phoneNumber); const QString &phoneNumber,
const SharedContact::VcardItems &vcardItems);
~MediaContact(); ~MediaContact();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;

View file

@ -211,12 +211,59 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
const MTPMessageMedia &media) { const MTPMessageMedia &media) {
using Result = std::unique_ptr<Data::Media>; using Result = std::unique_ptr<Data::Media>;
return media.match([&](const MTPDmessageMediaContact &media) -> Result { return media.match([&](const MTPDmessageMediaContact &media) -> Result {
const auto vcardItems = [&] {
using Type = Data::SharedContact::VcardItemType;
auto items = Data::SharedContact::VcardItems();
for (const auto &item : qs(media.vvcard()).split('\n')) {
const auto parts = item.split(':');
if (parts.size() == 2) {
const auto &type = parts.front();
const auto &value = parts[1];
if (type.startsWith("TEL")) {
const auto telType = type.contains("PREF")
? Type::PhoneMain
: type.contains("HOME")
? Type::PhoneHome
: type.contains("WORK")
? Type::PhoneWork
: (type.contains("CELL")
|| type.contains("MOBILE"))
? Type::PhoneMobile
: type.contains("OTHER")
? Type::PhoneOther
: Type::Phone;
items[telType] = value;
} else if (type.startsWith("EMAIL")) {
items[Type::Email] = value;
} else if (type.startsWith("URL")) {
items[Type::Url] = value;
} else if (type.startsWith("NOTE")) {
items[Type::Note] = value;
} else if (type.startsWith("ORG")) {
items[Type::Organization] = value;
items[Type::Organization].replace(';', ' ');
} else if (type.startsWith("ADR")) {
items[Type::Address] = value;
} else if (type.startsWith("BDAY")) {
items[Type::Birthday] = value;
} else if (type.startsWith("N")) {
items[Type::Birthday] = value;
items[Type::Birthday].replace(';', ' ');
}
}
}
return items;
}();
return std::make_unique<Data::MediaContact>( return std::make_unique<Data::MediaContact>(
item, item,
media.vuser_id().v, media.vuser_id().v,
qs(media.vfirst_name()), qs(media.vfirst_name()),
qs(media.vlast_name()), qs(media.vlast_name()),
qs(media.vphone_number())); qs(media.vphone_number()),
vcardItems);
}, [&](const MTPDmessageMediaGeo &media) -> Result { }, [&](const MTPDmessageMediaGeo &media) -> Result {
return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result { return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
return std::make_unique<Data::MediaLocation>( return std::make_unique<Data::MediaLocation>(

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
#include "core/click_handler_types.h" // ClickHandlerContext #include "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_media_types.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "history/history.h" #include "history/history.h"
@ -18,16 +19,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media_common.h" #include "history/view/media/history_view_media_common.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "ui/chat/chat_style.h" #include "ui/chat/chat_style.h"
#include "ui/empty_userpic.h" #include "ui/empty_userpic.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/power_saving.h" #include "ui/power_saving.h"
#include "ui/rect.h" #include "ui/rect.h"
#include "ui/text/format_values.h" // Ui::FormatPhone #include "ui/text/format_values.h" // Ui::FormatPhone
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "ui/text/text_utilities.h" // Ui::Text::Wrapped.
#include "ui/vertical_list.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
namespace HistoryView { namespace HistoryView {
namespace { namespace {
@ -103,33 +108,116 @@ ClickHandlerPtr AddContactClickHandler(not_null<HistoryItem*> item) {
return clickHandlerPtr; return clickHandlerPtr;
} }
[[nodiscard]] Fn<void(not_null<Ui::GenericBox*>)> VcardBoxFactory(
const Data::SharedContact::VcardItems &vcardItems) {
if (vcardItems.empty()) {
return nullptr;
}
return [=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_contact_details_title());
const auto &stL = st::proxyApplyBoxLabel;
const auto &stSubL = st::boxDividerLabel;
const auto add = [&](const QString &s, tr::phrase<> phrase) {
if (!s.isEmpty()) {
const auto label = box->addRow(
object_ptr<Ui::FlatLabel>(box, s, stL));
box->addRow(object_ptr<Ui::FlatLabel>(box, phrase(), stSubL));
Ui::AddSkip(box->verticalLayout());
Ui::AddSkip(box->verticalLayout());
return label;
}
return (Ui::FlatLabel*)(nullptr);
};
for (const auto &[type, value] : vcardItems) {
using Type = Data::SharedContact::VcardItemType;
const auto isPhoneType = (type == Type::Phone)
|| (type == Type::PhoneMain)
|| (type == Type::PhoneHome)
|| (type == Type::PhoneMobile)
|| (type == Type::PhoneWork)
|| (type == Type::PhoneOther);
const auto typePhrase = (type == Type::Phone)
? tr::lng_contact_details_phone
: (type == Type::PhoneMain)
? tr::lng_contact_details_phone_main
: (type == Type::PhoneHome)
? tr::lng_contact_details_phone_home
: (type == Type::PhoneMobile)
? tr::lng_contact_details_phone_mobile
: (type == Type::PhoneWork)
? tr::lng_contact_details_phone_work
: (type == Type::PhoneOther)
? tr::lng_contact_details_phone_other
: (type == Type::Email)
? tr::lng_contact_details_email
: (type == Type::Address)
? tr::lng_contact_details_address
: (type == Type::Url)
? tr::lng_contact_details_url
: (type == Type::Note)
? tr::lng_contact_details_note
: (type == Type::Birthday)
? tr::lng_contact_details_birthday
: (type == Type::Organization)
? tr::lng_contact_details_organization
: tr::lng_payments_info_name;
if (const auto label = add(value, typePhrase)) {
const auto copyText = isPhoneType
? tr::lng_profile_copy_phone
: (type == Type::Email)
? tr::lng_context_copy_email
: (type == Type::Url)
? tr::lng_context_copy_link
: (type == Type::Name)
? tr::lng_profile_copy_fullname
: tr::lng_context_copy_text;
label->setContextCopyText(copyText(tr::now));
if (type == Type::Email) {
label->setMarkedText(
Ui::Text::Wrapped({ value }, EntityType::Email));
} else if (type == Type::Url) {
label->setMarkedText(
Ui::Text::Wrapped({ value }, EntityType::Url));
} else if (isPhoneType) {
label->setText(Ui::FormatPhone(value));
}
using Request = Ui::FlatLabel::ContextMenuRequest;
label->setContextMenuHook([=](Request r) {
label->fillContextMenu(r.link
? r
: Request{ .menu = r.menu, .fullSelection = true });
});
}
}
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
};
}
} // namespace } // namespace
Contact::Contact( Contact::Contact(
not_null<Element*> parent, not_null<Element*> parent,
UserId userId, const Data::SharedContact &data)
const QString &first,
const QString &last,
const QString &phone)
: Media(parent) : Media(parent)
, _st(st::historyPagePreview) , _st(st::historyPagePreview)
, _pixh(st::contactsPhotoSize) , _pixh(st::contactsPhotoSize)
, _userId(userId) { , _userId(data.userId)
history()->owner().registerContactView(userId, parent); , _vcardBoxFactory(VcardBoxFactory(data.vcardItems)) {
history()->owner().registerContactView(data.userId, parent);
_nameLine.setText( _nameLine.setText(
st::webPageTitleStyle, st::webPageTitleStyle,
tr::lng_full_name( tr::lng_full_name(
tr::now, tr::now,
lt_first_name, lt_first_name,
first, data.firstName,
lt_last_name, lt_last_name,
last).trimmed(), data.lastName).trimmed(),
Ui::WebpageTextTitleOptions()); Ui::WebpageTextTitleOptions());
_phoneLine.setText( _phoneLine.setText(
st::webPageDescriptionStyle, st::webPageDescriptionStyle,
Ui::FormatPhone(phone), Ui::FormatPhone(data.phoneNumber),
Ui::WebpageTextTitleOptions()); Ui::WebpageTextTitleOptions());
#if 0 // No info. #if 0 // No info.
@ -188,16 +276,22 @@ QSize Contact::countOptimalSize() {
}); });
} }
_mainButton.link = _buttons.front().link; _mainButton.link = _buttons.front().link;
} else { } else if (const auto vcardBoxFactory = _vcardBoxFactory) {
#if 0 // Can't view contact. const auto view = tr::lng_contact_details_button(tr::now).toUpper();
const auto view = tr::lng_profile_add_contact(tr::now).toUpper();
_buttons.push_back({ _buttons.push_back({
view, view,
st::semiboldFont->width(view), st::semiboldFont->width(view),
AddContactClickHandler(_parent->data()), AddContactClickHandler(_parent->data()),
}); });
#endif _mainButton.link = std::make_shared<LambdaClickHandler>([=](
_mainButton.link = nullptr; const ClickContext &context) {
const auto my = context.other.value<ClickHandlerContext>();
if (const auto controller = my.sessionWindow.get()) {
controller->uiShow()->show(Box([=](not_null<Ui::GenericBox*> box) {
vcardBoxFactory(box);
}));
}
});
} }
const auto padding = inBubblePadding() + innerMargin(); const auto padding = inBubblePadding() + innerMargin();

View file

@ -10,8 +10,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/media/history_view_media.h" #include "history/view/media/history_view_media.h"
#include "ui/userpic_view.h" #include "ui/userpic_view.h"
namespace Data {
struct SharedContact;
} // namespace Data
namespace Ui { namespace Ui {
class EmptyUserpic; class EmptyUserpic;
class GenericBox;
class RippleAnimation; class RippleAnimation;
} // namespace Ui } // namespace Ui
@ -21,10 +26,7 @@ class Contact final : public Media {
public: public:
Contact( Contact(
not_null<Element*> parent, not_null<Element*> parent,
UserId userId, const Data::SharedContact &data);
const QString &first,
const QString &last,
const QString &phone);
~Contact(); ~Contact();
void draw(Painter &p, const PaintContext &context) const override; void draw(Painter &p, const PaintContext &context) const override;
@ -76,6 +78,8 @@ private:
Ui::Text::String _phoneLine; Ui::Text::String _phoneLine;
Ui::Text::String _infoLine; Ui::Text::String _infoLine;
Fn<void(not_null<Ui::GenericBox*>)> _vcardBoxFactory;
struct Button { struct Button {
QString text; QString text;
int width = 0; int width = 0;