diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 68231eeab..0748ac25d 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3642,6 +3642,20 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_new_contact_from_request_group" = "{user} is an admin of {name}, a group you requested to join."; "lng_new_contact_about_status" = "This account uses {emoji} as a custom status next to its\nname. Such emoji statuses are available to all\nsubscribers of {link}."; "lng_new_contact_about_status_link" = "Telegram Premium"; +"lng_new_contact_not_contact" = "Not a contact"; +"lng_new_contact_phone_number" = "Phone number"; +"lng_new_contact_registration" = "Registration"; +"lng_new_contact_common_groups" = "Common groups"; +"lng_new_contact_not_official" = "Not an official account"; +"lng_new_contact_updated_name" = "User updated name {when}"; +"lng_new_contact_updated_photo" = "User updated photo {when}"; +"lng_new_contact_updated_now" = "less than an hour ago"; +"lng_new_contact_updated_hours#one" = "{count} hour ago"; +"lng_new_contact_updated_hours#other" = "{count} hours ago"; +"lng_new_contact_updated_days#one" = "{count} day ago"; +"lng_new_contact_updated_days#other" = "{count} days ago"; +"lng_new_contact_updated_months#one" = "{count} month ago"; +"lng_new_contact_updated_months#other" = "{count} months ago"; "lng_from_request_title_channel" = "Response to your join request"; "lng_from_request_title_group" = "Response to your join request"; "lng_from_request_body" = "You received this message because you requested to join {name} on {date}."; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp index 988c12dd8..5c7536083 100644 --- a/Telegram/SourceFiles/data/data_peer.cpp +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -66,6 +66,28 @@ using UpdateFlag = Data::PeerUpdate::Flag; return session->appConfig().ignoredRestrictionReasons(); } +[[nodiscard]] int ParseRegistrationDate(const QString &text) { + // MM.YYYY + if (text.size() != 7 || text[2] != '.') { + return 0; + } + const auto month = text.mid(0, 2).toInt(); + const auto year = text.mid(3, 4).toInt(); + return (year > 2012 && year < 2100 && month > 0 && month <= 12) + ? (year * 100) + month + : 0; +} + +[[nodiscard]] int RegistrationYear(int date) { + const auto year = date / 100; + return (year > 2012 && year < 2100) ? year : 0; +} + +[[nodiscard]] int RegistrationMonth(int date) { + const auto month = date % 100; + return (month > 0 && month <= 12) ? month : 0; +} + } // namespace namespace Data { @@ -734,7 +756,9 @@ void PeerData::checkFolder(FolderId folderId) { void PeerData::clearBusinessBot() { if (const auto details = _barDetails.get()) { - if (details->requestChatDate || details->paysPerMessage) { + if (details->requestChatDate + || details->paysPerMessage + || !details->phoneCountryCode.isEmpty()) { details->businessBot = nullptr; details->businessBotManageUrl = QString(); } else { @@ -780,12 +804,24 @@ void PeerData::setBarSettings(const MTPPeerSettings &data) { const auto wasPaysPerMessage = paysPerMessage(); if (!data.vbusiness_bot_id() && !data.vrequest_chat_title() - && !data.vcharge_paid_message_stars()) { + && !data.vcharge_paid_message_stars() + && !data.vphone_country() + && !data.vregistration_month() + && !data.vname_change_date() + && !data.vphoto_change_date()) { _barDetails = nullptr; } else if (!_barDetails) { _barDetails = std::make_unique(); } if (_barDetails) { + _barDetails->phoneCountryCode + = qs(data.vphone_country().value_or_empty()); + _barDetails->registrationDate = ParseRegistrationDate( + data.vregistration_month().value_or_empty()); + _barDetails->nameChangeDate + = data.vname_change_date().value_or_empty(); + _barDetails->photoChangeDate + = data.vphoto_change_date().value_or_empty(); _barDetails->requestChatTitle = qs(data.vrequest_chat_title().value_or_empty()); _barDetails->requestChatDate @@ -835,7 +871,9 @@ int PeerData::paysPerMessage() const { void PeerData::clearPaysPerMessage() { if (const auto details = _barDetails.get()) { if (details->paysPerMessage) { - if (details->businessBot || details->requestChatDate) { + if (details->businessBot + || details->requestChatDate + || !details->phoneCountryCode.isEmpty()) { details->paysPerMessage = 0; } else { _barDetails = nullptr; @@ -863,6 +901,28 @@ QString PeerData::businessBotManageUrl() const { return _barDetails ? _barDetails->businessBotManageUrl : QString(); } +QString PeerData::phoneCountryCode() const { + return _barDetails ? _barDetails->phoneCountryCode : QString(); +} + +int PeerData::registrationMonth() const { + return _barDetails + ? RegistrationMonth(_barDetails->registrationDate) + : 0; +} + +int PeerData::registrationYear() const { + return _barDetails ? RegistrationYear(_barDetails->registrationDate) : 0; +} + +TimeId PeerData::nameChangeDate() const { + return _barDetails ? _barDetails->nameChangeDate : 0; +} + +TimeId PeerData::photoChangeDate() const { + return _barDetails ? _barDetails->photoChangeDate : 0; +} + bool PeerData::changeColorIndex( const tl::conditional &cloudColorIndex) { return cloudColorIndex diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index d0b6d687a..21aad4cde 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -173,6 +173,10 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; }; using PeerBarSettings = base::flags; struct PeerBarDetails { + QString phoneCountryCode; + int registrationDate = 0; // YYYYMM or 0, YYYY > 2012, MM > 0. + TimeId nameChangeDate = 0; + TimeId photoChangeDate = 0; QString requestChatTitle; TimeId requestChatDate; UserData *businessBot = nullptr; @@ -420,6 +424,11 @@ public: [[nodiscard]] UserData *businessBot() const; [[nodiscard]] QString businessBotManageUrl() const; void clearBusinessBot(); + [[nodiscard]] QString phoneCountryCode() const; + [[nodiscard]] int registrationMonth() const; + [[nodiscard]] int registrationYear() const; + [[nodiscard]] TimeId nameChangeDate() const; + [[nodiscard]] TimeId photoChangeDate() const; enum class TranslationFlag : uchar { Unknown, diff --git a/Telegram/SourceFiles/history/history_inner_widget.cpp b/Telegram/SourceFiles/history/history_inner_widget.cpp index 61ac0fa4e..983ec2839 100644 --- a/Telegram/SourceFiles/history/history_inner_widget.cpp +++ b/Telegram/SourceFiles/history/history_inner_widget.cpp @@ -4350,6 +4350,9 @@ void HistoryInner::refreshAboutView(bool force) { if (!info->inited) { session().api().requestFullPeer(user); } + } else if (!user->isContact() + && !user->phoneCountryCode().isEmpty()) { + refresh(); } else if (!historyHeight()) { if (user->starsPerMessage() > 0 || (user->requiresPremiumToWrite() diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.cpp b/Telegram/SourceFiles/history/view/history_view_about_view.cpp index a76361df5..47e24e07a 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.cpp +++ b/Telegram/SourceFiles/history/view/history_view_about_view.cpp @@ -14,7 +14,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/premium_preview_box.h" #include "chat_helpers/stickers_lottie.h" #include "core/click_handler_types.h" +#include "core/ui_integration.h" +#include "countries/countries_instance.h" #include "data/business/data_business_common.h" +#include "data/stickers/data_custom_emoji.h" #include "data/data_document.h" #include "data/data_session.h" #include "data/data_user.h" @@ -22,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_service_box.h" #include "history/view/media/history_view_sticker_player_abstract.h" #include "history/view/media/history_view_sticker.h" +#include "history/view/media/history_view_unique_gift.h" #include "history/view/history_view_element.h" #include "history/history.h" #include "history/history_item.h" @@ -43,6 +47,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { +constexpr auto kLabelOpacity = 0.85; + class EmptyChatLockedBox final : public ServiceBoxContent , public base::has_weak_ptr { @@ -156,6 +162,79 @@ auto GenerateChatIntro( }; } +auto GenerateNewPeerInfo( + not_null parent, + Element *replacing, + not_null user) +-> Fn, + Fn)>)> { + return [=]( + not_null media, + Fn)> push) { + const auto normalFg = [](const PaintContext &context) { + return context.st->msgServiceFg()->c; + }; + const auto fadedFg = [](const PaintContext &context) { + auto result = context.st->msgServiceFg()->c; + result.setAlphaF(result.alphaF() * kLabelOpacity); + return result; + }; + push(std::make_unique( + Ui::Text::Bold(user->name()), + st::newPeerTitleMargin)); + push(std::make_unique( + tr::lng_new_contact_not_contact(tr::now, Ui::Text::WithEntities), + st::newPeerSubtitleMargin, + fadedFg)); + + auto entries = std::vector(); + const auto country = user->phoneCountryCode(); + if (!country.isEmpty()) { + const auto &countries = Countries::Instance(); + const auto name = countries.countryNameByISO2(country); + const auto flag = countries.flagEmojiByISO2(country); + entries.push_back({ + tr::lng_new_contact_phone_number(tr::now), + Ui::Text::Bold(flag + QChar(0xA0) + name), + }); + } + const auto month = user->registrationMonth(); + const auto year = user->registrationYear(); + if (month && year) { + entries.push_back({ + tr::lng_new_contact_registration(tr::now), + Ui::Text::Bold(langMonthOfYearFull(month, year)), + }); + } + + push(std::make_unique( + std::move(entries), + st::newPeerSubtitleMargin, + fadedFg, + normalFg)); + + const auto context = Core::MarkedTextContext{ + .session = &parent->history()->session(), + .customEmojiRepaint = [parent] { parent->repaint(); }, + }; + const auto details = user->botVerifyDetails(); + const auto text = details + ? Data::SingleCustomEmoji( + details->iconId + ).append(' ').append(details->description) + : TextWithEntities().append( + tr::lng_new_contact_not_official(tr::now)); + push(std::make_unique( + text, + st::newPeerSubtitleMargin, + fadedFg, + st::defaultTextStyle, + base::flat_map(), + context)); + }; +} + EmptyChatLockedBox::EmptyChatLockedBox(not_null parent, Type type) : _parent(parent) , _type(type) { @@ -277,7 +356,15 @@ bool AboutView::refresh() { const auto user = _history->peer->asUser(); const auto info = user ? user->botInfo.get() : nullptr; if (!info) { - if (user && !user->isSelf() && _history->isDisplayedEmpty()) { + if (user + && !user->isContact() + && !user->phoneCountryCode().isEmpty()) { + if (_item) { + return false; + } + setItem(makeNewPeerInfo(user), nullptr); + return true; + } else if (user && !user->isSelf() && _history->isDisplayedEmpty()) { if (_item) { return false; } else if (user->requiresPremiumToWrite() @@ -396,6 +483,27 @@ void AboutView::setItem(AdminLog::OwnedItem item, DocumentData *sticker) { toggleStickerRegistered(true); } +AdminLog::OwnedItem AboutView::makeNewPeerInfo(not_null user) { + const auto text = user->name(); + const auto item = _history->makeMessage({ + .id = _history->nextNonHistoryEntryId(), + .flags = (MessageFlag::FakeAboutView + | MessageFlag::FakeHistoryItem + | MessageFlag::Local), + .from = _history->peer->id, + }, PreparedServiceText{ { text }}); + + auto owned = AdminLog::OwnedItem(_delegate, item); + owned->overrideMedia(std::make_unique( + owned.get(), + GenerateNewPeerInfo(owned.get(), _item.get(), user), + HistoryView::MediaGenericDescriptor{ + .service = true, + .hideServiceText = true, + })); + return owned; +} + AdminLog::OwnedItem AboutView::makeAboutVerifyCodes() { return makeAboutSimple( tr::lng_verification_codes_about(tr::now, Ui::Text::RichLangValue)); diff --git a/Telegram/SourceFiles/history/view/history_view_about_view.h b/Telegram/SourceFiles/history/view/history_view_about_view.h index 57244fb1f..2b304d92b 100644 --- a/Telegram/SourceFiles/history/view/history_view_about_view.h +++ b/Telegram/SourceFiles/history/view/history_view_about_view.h @@ -42,6 +42,8 @@ private: PhotoData *photo = nullptr); [[nodiscard]] AdminLog::OwnedItem makePremiumRequired(); [[nodiscard]] AdminLog::OwnedItem makeStarsPerMessage(int stars); + [[nodiscard]] AdminLog::OwnedItem makeNewPeerInfo( + not_null user); [[nodiscard]] AdminLog::OwnedItem makeBlocked(); void makeIntro(not_null user); void setItem(AdminLog::OwnedItem item, DocumentData *sticker); diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp index 27ca9c545..015ef507a 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.cpp @@ -39,60 +39,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryView { namespace { -class TextPartColored final : public MediaGenericTextPart { -public: - TextPartColored( - TextWithEntities text, - QMargins margins, - QColor color, - const style::TextStyle &st = st::defaultTextStyle, - const base::flat_map &links = {}, - const std::any &context = {}); - -private: - void setupPen( - Painter &p, - not_null owner, - const PaintContext &context) const override; - - QColor _color; - -}; - -class AttributeTable final : public MediaGenericPart { -public: - struct Entry { - QString label; - QString value; - }; - - AttributeTable( - std::vector entries, - QMargins margins, - QColor labelColor); - - void draw( - Painter &p, - not_null owner, - const PaintContext &context, - int outerWidth) const override; - - QSize countOptimalSize() override; - QSize countCurrentSize(int newWidth) override; - -private: - struct Part { - Ui::Text::String label; - Ui::Text::String value; - }; - - std::vector _parts; - QMargins _margins; - QColor _labelColor; - int _valueLeft = 0; - -}; - class ButtonPart final : public MediaGenericPart { public: ButtonPart( @@ -270,116 +216,7 @@ QSize ButtonPart::countCurrentSize(int newWidth) { return optimalSize(); } -TextPartColored::TextPartColored( - TextWithEntities text, - QMargins margins, - QColor color, - const style::TextStyle &st, - const base::flat_map &links, - const std::any &context) -: MediaGenericTextPart(text, margins, st, links, context) -, _color(color) { -} - -void TextPartColored::setupPen( - Painter &p, - not_null owner, - const PaintContext &context) const { - p.setPen(_color); -} - -AttributeTable::AttributeTable( - std::vector entries, - QMargins margins, - QColor labelColor) -: _margins(margins) -, _labelColor(labelColor) { - for (const auto &entry : entries) { - _parts.emplace_back(); - auto &part = _parts.back(); - part.label.setText(st::defaultTextStyle, entry.label); - part.value.setMarkedText( - st::defaultTextStyle, - Ui::Text::Bold(entry.value)); - } -} - -void AttributeTable::draw( - Painter &p, - not_null owner, - const PaintContext &context, - int outerWidth) const { - const auto labelRight = _valueLeft - st::chatUniqueTableSkip; - const auto palette = &context.st->serviceTextPalette(); - auto top = _margins.top(); - const auto paint = [&]( - const Ui::Text::String &text, - int left, - int availableWidth, - style::align align) { - text.draw(p, { - .position = { left, top }, - .outerWidth = outerWidth, - .availableWidth = availableWidth, - .align = align, - .palette = palette, - .spoiler = Ui::Text::DefaultSpoilerCache(), - .now = context.now, - .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), - .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), - .elisionLines = 1, - }); - }; - const auto forLabel = labelRight - _margins.left(); - const auto forValue = width() - _valueLeft - _margins.right(); - const auto white = QColor(255, 255, 255); - for (const auto &part : _parts) { - p.setPen(_labelColor); - paint(part.label, _margins.left(), forLabel, style::al_topright); - p.setPen(white); - paint(part.value, _valueLeft, forValue, style::al_topleft); - top += st::normalFont->height + st::chatUniqueRowSkip; - } -} - -QSize AttributeTable::countOptimalSize() { - auto maxLabel = 0; - auto maxValue = 0; - for (const auto &part : _parts) { - maxLabel = std::max(maxLabel, part.label.maxWidth()); - maxValue = std::max(maxValue, part.value.maxWidth()); - } - const auto skip = st::chatUniqueTableSkip; - const auto row = st::normalFont->height + st::chatUniqueRowSkip; - const auto height = int(_parts.size()) * row - st::chatUniqueRowSkip; - return { - _margins.left() + maxLabel + skip + maxValue + _margins.right(), - _margins.top() + height + _margins.bottom(), - }; -} - -QSize AttributeTable::countCurrentSize(int newWidth) { - const auto skip = st::chatUniqueTableSkip; - const auto width = newWidth - _margins.left() - _margins.right() - skip; - auto maxLabel = 0; - auto maxValue = 0; - for (const auto &part : _parts) { - maxLabel = std::max(maxLabel, part.label.maxWidth()); - maxValue = std::max(maxValue, part.value.maxWidth()); - } - if (width <= 0 || !maxLabel) { - _valueLeft = _margins.left(); - } else if (!maxValue) { - _valueLeft = newWidth - _margins.right(); - } else { - _valueLeft = _margins.left() - + int((int64(maxLabel) * width) / (maxLabel + maxValue)) - + skip; - } - return { newWidth, minHeight() }; -} - -}; // namespace +} // namespace auto GenerateUniqueGiftMedia( not_null parent, @@ -402,7 +239,7 @@ auto GenerateUniqueGiftMedia( push(std::make_unique( std::move(text), margins, - color, + [color](const auto&) { return color; }, st)); }; @@ -447,15 +284,19 @@ auto GenerateUniqueGiftMedia( gift->backdrop.textColor, st::chatUniqueTextPadding); + const auto name = [](const Data::UniqueGiftAttribute &value) { + return Ui::Text::Bold(value.name); + }; auto attributes = std::vector{ - { tr::lng_gift_unique_model(tr::now), gift->model.name }, - { tr::lng_gift_unique_backdrop(tr::now), gift->backdrop.name }, - { tr::lng_gift_unique_symbol(tr::now), gift->pattern.name }, + { tr::lng_gift_unique_model(tr::now), name(gift->model) }, + { tr::lng_gift_unique_backdrop(tr::now), name(gift->backdrop) }, + { tr::lng_gift_unique_symbol(tr::now), name(gift->pattern) }, }; push(std::make_unique( std::move(attributes), st::chatUniqueTextPadding, - gift->backdrop.textColor)); + [c = gift->backdrop.textColor](const auto&) { return c; }, + [](const auto&) { return QColor(255, 255, 255); })); auto link = OpenStarGiftLink(parent->data()); push(std::make_unique( @@ -594,4 +435,117 @@ std::unique_ptr MakeGenericButtonPart( return std::make_unique(text, margins, repaint, link, bg); } +TextPartColored::TextPartColored( + TextWithEntities text, + QMargins margins, + Fn color, + const style::TextStyle &st, + const base::flat_map &links, + const std::any &context) +: MediaGenericTextPart(text, margins, st, links, context) +, _color(std::move(color)) { +} + +void TextPartColored::setupPen( + Painter &p, + not_null owner, + const PaintContext &context) const { + p.setPen(_color(context)); +} + +AttributeTable::AttributeTable( + std::vector entries, + QMargins margins, + Fn labelColor, + Fn valueColor, + const std::any &context) +: _margins(margins) +, _labelColor(std::move(labelColor)) +, _valueColor(std::move(valueColor)) { + for (const auto &entry : entries) { + _parts.emplace_back(); + auto &part = _parts.back(); + part.label.setText(st::defaultTextStyle, entry.label); + part.value.setMarkedText( + st::defaultTextStyle, + entry.value, + kMarkupTextOptions, + context); + } +} + +void AttributeTable::draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const { + const auto labelRight = _valueLeft - st::chatUniqueTableSkip; + const auto palette = &context.st->serviceTextPalette(); + auto top = _margins.top(); + const auto paint = [&]( + const Ui::Text::String &text, + int left, + int availableWidth, + style::align align) { + text.draw(p, { + .position = { left, top }, + .outerWidth = outerWidth, + .availableWidth = availableWidth, + .align = align, + .palette = palette, + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), + .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), + .elisionLines = 1, + }); + }; + const auto forLabel = labelRight - _margins.left(); + const auto forValue = width() - _valueLeft - _margins.right(); + for (const auto &part : _parts) { + p.setPen(_labelColor(context)); + paint(part.label, _margins.left(), forLabel, style::al_topright); + p.setPen(_valueColor(context)); + paint(part.value, _valueLeft, forValue, style::al_topleft); + top += st::normalFont->height + st::chatUniqueRowSkip; + } +} + +QSize AttributeTable::countOptimalSize() { + auto maxLabel = 0; + auto maxValue = 0; + for (const auto &part : _parts) { + maxLabel = std::max(maxLabel, part.label.maxWidth()); + maxValue = std::max(maxValue, part.value.maxWidth()); + } + const auto skip = st::chatUniqueTableSkip; + const auto row = st::normalFont->height + st::chatUniqueRowSkip; + const auto height = int(_parts.size()) * row - st::chatUniqueRowSkip; + return { + _margins.left() + maxLabel + skip + maxValue + _margins.right(), + _margins.top() + height + _margins.bottom(), + }; +} + +QSize AttributeTable::countCurrentSize(int newWidth) { + const auto skip = st::chatUniqueTableSkip; + const auto width = newWidth - _margins.left() - _margins.right() - skip; + auto maxLabel = 0; + auto maxValue = 0; + for (const auto &part : _parts) { + maxLabel = std::max(maxLabel, part.label.maxWidth()); + maxValue = std::max(maxValue, part.value.maxWidth()); + } + if (width <= 0 || !maxLabel) { + _valueLeft = _margins.left(); + } else if (!maxValue) { + _valueLeft = newWidth - _margins.right(); + } else { + _valueLeft = _margins.left() + + int((int64(maxLabel) * width) / (maxLabel + maxValue)) + + skip; + } + return { newWidth, minHeight() }; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h index 635190af1..269065076 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h +++ b/Telegram/SourceFiles/history/view/media/history_view_unique_gift.h @@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "history/view/media/history_view_media_generic.h" + class Painter; namespace Data { @@ -55,4 +57,62 @@ class MediaGenericPart; ClickHandlerPtr link, QColor bg = QColor(0, 0, 0, 0)); + +class TextPartColored : public MediaGenericTextPart { +public: + TextPartColored( + TextWithEntities text, + QMargins margins, + Fn color, + const style::TextStyle &st = st::defaultTextStyle, + const base::flat_map &links = {}, + const std::any &context = {}); + +private: + void setupPen( + Painter &p, + not_null owner, + const PaintContext &context) const override; + + Fn _color; + +}; + +class AttributeTable final : public MediaGenericPart { +public: + struct Entry { + QString label; + TextWithEntities value; + }; + + AttributeTable( + std::vector entries, + QMargins margins, + Fn labelColor, + Fn valueColor, + const std::any &context = {}); + + void draw( + Painter &p, + not_null owner, + const PaintContext &context, + int outerWidth) const override; + + QSize countOptimalSize() override; + QSize countCurrentSize(int newWidth) override; + +private: + struct Part { + Ui::Text::String label; + Ui::Text::String value; + }; + + std::vector _parts; + QMargins _margins; + Fn _labelColor; + Fn _valueColor; + int _valueLeft = 0; + +}; + } // namespace HistoryView diff --git a/Telegram/SourceFiles/ui/chat/chat.style b/Telegram/SourceFiles/ui/chat/chat.style index fefee68f3..b07eab0be 100644 --- a/Telegram/SourceFiles/ui/chat/chat.style +++ b/Telegram/SourceFiles/ui/chat/chat.style @@ -1225,3 +1225,6 @@ chatUniqueRowSkip: 4px; chatUniqueButtonPadding: margins(12px, 4px, 12px, 16px); markupWebview: icon {{ "chat/markup_webview", windowFg }}; + +newPeerTitleMargin: margins(11px, 16px, 11px, 6px); +newPeerSubtitleMargin: margins(11px, 0px, 11px, 16px);