Initial new peer information display.

This commit is contained in:
John Preston 2025-03-06 18:40:50 +04:00
parent a1e555267e
commit 0fc8229be1
9 changed files with 386 additions and 173 deletions

View file

@ -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}.";

View file

@ -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<PeerBarDetails>();
}
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<MTPint> &cloudColorIndex) {
return cloudColorIndex

View file

@ -173,6 +173,10 @@ inline constexpr bool is_flag_type(PeerBarSetting) { return true; };
using PeerBarSettings = base::flags<PeerBarSetting>;
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,

View file

@ -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()

View file

@ -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<Element*> parent,
Element *replacing,
not_null<UserData*> user)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> 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<MediaGenericTextPart>(
Ui::Text::Bold(user->name()),
st::newPeerTitleMargin));
push(std::make_unique<TextPartColored>(
tr::lng_new_contact_not_contact(tr::now, Ui::Text::WithEntities),
st::newPeerSubtitleMargin,
fadedFg));
auto entries = std::vector<AttributeTable::Entry>();
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<AttributeTable>(
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<TextPartColored>(
text,
st::newPeerSubtitleMargin,
fadedFg,
st::defaultTextStyle,
base::flat_map<uint16, ClickHandlerPtr>(),
context));
};
}
EmptyChatLockedBox::EmptyChatLockedBox(not_null<Element*> 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<UserData*> 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<HistoryView::MediaGeneric>(
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));

View file

@ -42,6 +42,8 @@ private:
PhotoData *photo = nullptr);
[[nodiscard]] AdminLog::OwnedItem makePremiumRequired();
[[nodiscard]] AdminLog::OwnedItem makeStarsPerMessage(int stars);
[[nodiscard]] AdminLog::OwnedItem makeNewPeerInfo(
not_null<UserData*> user);
[[nodiscard]] AdminLog::OwnedItem makeBlocked();
void makeIntro(not_null<UserData*> user);
void setItem(AdminLog::OwnedItem item, DocumentData *sticker);

View file

@ -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<uint16, ClickHandlerPtr> &links = {},
const std::any &context = {});
private:
void setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const override;
QColor _color;
};
class AttributeTable final : public MediaGenericPart {
public:
struct Entry {
QString label;
QString value;
};
AttributeTable(
std::vector<Entry> entries,
QMargins margins,
QColor labelColor);
void draw(
Painter &p,
not_null<const MediaGeneric*> 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<Part> _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<uint16, ClickHandlerPtr> &links,
const std::any &context)
: MediaGenericTextPart(text, margins, st, links, context)
, _color(color) {
}
void TextPartColored::setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const {
p.setPen(_color);
}
AttributeTable::AttributeTable(
std::vector<Entry> 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<const MediaGeneric*> 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<Element*> parent,
@ -402,7 +239,7 @@ auto GenerateUniqueGiftMedia(
push(std::make_unique<TextPartColored>(
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<AttributeTable::Entry>{
{ 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<AttributeTable>(
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<ButtonPart>(
@ -594,4 +435,117 @@ std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
return std::make_unique<ButtonPart>(text, margins, repaint, link, bg);
}
TextPartColored::TextPartColored(
TextWithEntities text,
QMargins margins,
Fn<QColor(const PaintContext &)> color,
const style::TextStyle &st,
const base::flat_map<uint16, ClickHandlerPtr> &links,
const std::any &context)
: MediaGenericTextPart(text, margins, st, links, context)
, _color(std::move(color)) {
}
void TextPartColored::setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const {
p.setPen(_color(context));
}
AttributeTable::AttributeTable(
std::vector<Entry> entries,
QMargins margins,
Fn<QColor(const PaintContext &)> labelColor,
Fn<QColor(const PaintContext &)> 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<const MediaGeneric*> 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

View file

@ -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<QColor(const PaintContext &)> color,
const style::TextStyle &st = st::defaultTextStyle,
const base::flat_map<uint16, ClickHandlerPtr> &links = {},
const std::any &context = {});
private:
void setupPen(
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const override;
Fn<QColor(const PaintContext &)> _color;
};
class AttributeTable final : public MediaGenericPart {
public:
struct Entry {
QString label;
TextWithEntities value;
};
AttributeTable(
std::vector<Entry> entries,
QMargins margins,
Fn<QColor(const PaintContext &)> labelColor,
Fn<QColor(const PaintContext &)> valueColor,
const std::any &context = {});
void draw(
Painter &p,
not_null<const MediaGeneric*> 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<Part> _parts;
QMargins _margins;
Fn<QColor(const PaintContext &)> _labelColor;
Fn<QColor(const PaintContext &)> _valueColor;
int _valueLeft = 0;
};
} // namespace HistoryView

View file

@ -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);