diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index e027f6da9b..0aea70cdb5 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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..."; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 3a8dc19434..feb8ad16dc 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -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 MediaContact::clone(not_null 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 MediaContact::createView( not_null message, not_null realParent, HistoryView::Element *replacing) { - return std::make_unique( - message, - _contact.userId, - _contact.firstName, - _contact.lastName, - _contact.phoneNumber); + return std::make_unique(message, _contact); } MediaLocation::MediaLocation( diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index c486799cd4..d9f0773c02 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -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; + 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 clone(not_null parent) override; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ba8b4fb396..5a434c3a64 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -211,12 +211,59 @@ std::unique_ptr HistoryItem::CreateMedia( const MTPMessageMedia &media) { using Result = std::unique_ptr; 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( 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( diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp index 1e6654abde..d0fbfe7302 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_contact.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_contact.cpp @@ -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 item) { return clickHandlerPtr; } +[[nodiscard]] Fn)> VcardBoxFactory( + const Data::SharedContact::VcardItems &vcardItems) { + if (vcardItems.empty()) { + return nullptr; + } + return [=](not_null 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(box, s, stL)); + box->addRow(object_ptr(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 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([=]( + const ClickContext &context) { + const auto my = context.other.value(); + if (const auto controller = my.sessionWindow.get()) { + controller->uiShow()->show(Box([=](not_null box) { + vcardBoxFactory(box); + })); + } + }); } const auto padding = inBubblePadding() + innerMargin(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_contact.h b/Telegram/SourceFiles/history/view/media/history_view_contact.h index 2dd665b946..ada5877213 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_contact.h +++ b/Telegram/SourceFiles/history/view/media/history_view_contact.h @@ -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 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)> _vcardBoxFactory; + struct Button { QString text; int width = 0;