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_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
"lng_wnd_choose_program_menu" = "Choose Default Program...";

View File

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

View File

@ -47,11 +47,30 @@ enum class CallFinishReason : char {
Hangup,
};
struct SharedContact {
struct SharedContact final {
UserId userId = 0;
QString firstName;
QString lastName;
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 {
@ -308,7 +327,8 @@ public:
UserId userId,
const QString &firstName,
const QString &lastName,
const QString &phoneNumber);
const QString &phoneNumber,
const SharedContact::VcardItems &vcardItems);
~MediaContact();
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) {
using Result = std::unique_ptr<Data::Media>;
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>(
item,
media.vuser_id().v,
qs(media.vfirst_name()),
qs(media.vlast_name()),
qs(media.vphone_number()));
qs(media.vphone_number()),
vcardItems);
}, [&](const MTPDmessageMediaGeo &media) -> Result {
return media.vgeo().match([&](const MTPDgeoPoint &point) -> Result {
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 "core/click_handler_types.h" // ClickHandlerContext
#include "data/data_media_types.h"
#include "data/data_session.h"
#include "data/data_user.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 "lang/lang_keys.h"
#include "main/main_session.h"
#include "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "ui/chat/chat_style.h"
#include "ui/empty_userpic.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/power_saving.h"
#include "ui/rect.h"
#include "ui/text/format_values.h" // Ui::FormatPhone
#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 "styles/style_boxes.h"
#include "styles/style_chat.h"
#include "styles/style_layers.h"
namespace HistoryView {
namespace {
@ -103,33 +108,116 @@ ClickHandlerPtr AddContactClickHandler(not_null<HistoryItem*> item) {
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
Contact::Contact(
not_null<Element*> parent,
UserId userId,
const QString &first,
const QString &last,
const QString &phone)
const Data::SharedContact &data)
: Media(parent)
, _st(st::historyPagePreview)
, _pixh(st::contactsPhotoSize)
, _userId(userId) {
history()->owner().registerContactView(userId, parent);
, _userId(data.userId)
, _vcardBoxFactory(VcardBoxFactory(data.vcardItems)) {
history()->owner().registerContactView(data.userId, parent);
_nameLine.setText(
st::webPageTitleStyle,
tr::lng_full_name(
tr::now,
lt_first_name,
first,
data.firstName,
lt_last_name,
last).trimmed(),
data.lastName).trimmed(),
Ui::WebpageTextTitleOptions());
_phoneLine.setText(
st::webPageDescriptionStyle,
Ui::FormatPhone(phone),
Ui::FormatPhone(data.phoneNumber),
Ui::WebpageTextTitleOptions());
#if 0 // No info.
@ -188,16 +276,22 @@ QSize Contact::countOptimalSize() {
});
}
_mainButton.link = _buttons.front().link;
} else {
#if 0 // Can't view contact.
const auto view = tr::lng_profile_add_contact(tr::now).toUpper();
} else if (const auto vcardBoxFactory = _vcardBoxFactory) {
const auto view = tr::lng_contact_details_button(tr::now).toUpper();
_buttons.push_back({
view,
st::semiboldFont->width(view),
AddContactClickHandler(_parent->data()),
});
#endif
_mainButton.link = nullptr;
_mainButton.link = std::make_shared<LambdaClickHandler>([=](
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();

View File

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