Add unique gifts link preview.

This commit is contained in:
John Preston 2025-01-07 12:45:33 +04:00
parent 017535cf7b
commit 428e90a844
18 changed files with 189 additions and 48 deletions

View file

@ -5520,6 +5520,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_view_button_iv" = "Instant View";
"lng_view_button_stickerset" = "View stickers";
"lng_view_button_emojipack" = "View emoji";
"lng_view_button_collectible" = "View collectible";
"lng_sponsored_hide_ads" = "Hide";
"lng_sponsored_title" = "What are sponsored messages?";

View file

@ -313,6 +313,7 @@ PreviewWrap::PreviewWrap(
WebPageCollage(),
nullptr, // iv
nullptr, // stickerSet
nullptr, // uniqueGift
0, // duration
QString(), // author
false, // hasLargeMedia

View file

@ -208,8 +208,12 @@ auto GenerateGiftMedia(
Element *replacing,
not_null<PeerData*> recipient,
const GiftDetails &data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
-> 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 &descriptor = data.descriptor;
auto pushText = [&](
TextWithEntities text,

View file

@ -2375,13 +2375,13 @@ std::unique_ptr<HistoryView::Media> MediaGiftBox::createView(
not_null<HistoryView::Element*> message,
not_null<HistoryItem*> realParent,
HistoryView::Element *replacing) {
if (const auto raw = _data.unique.get()) {
if (const auto &unique = _data.unique) {
return std::make_unique<HistoryView::MediaGeneric>(
message,
HistoryView::GenerateUniqueGiftMedia(message, replacing, raw),
HistoryView::GenerateUniqueGiftMedia(message, replacing, unique),
HistoryView::MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftBoxSize.width(),
.paintBg = HistoryView::UniqueGiftBg(message, raw),
.paintBg = HistoryView::UniqueGiftBg(message, unique),
.service = true,
});
}

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "mainwidget.h"
#include "api/api_bot.h"
#include "api/api_premium.h"
#include "api/api_text_entities.h"
#include "api/api_user_names.h"
#include "chat_helpers/stickers_lottie.h"
@ -3464,6 +3465,7 @@ not_null<WebPageData*> Session::processWebpage(
WebPageCollage(),
nullptr,
nullptr,
nullptr,
0,
QString(),
false,
@ -3490,6 +3492,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage(),
nullptr,
nullptr,
nullptr,
0,
QString(),
false,
@ -3509,6 +3512,7 @@ not_null<WebPageData*> Session::webpage(
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration,
const QString &author,
bool hasLargeMedia,
@ -3528,6 +3532,7 @@ not_null<WebPageData*> Session::webpage(
std::move(collage),
std::move(iv),
std::move(stickerSet),
std::move(uniqueGift),
duration,
author,
hasLargeMedia,
@ -3562,6 +3567,7 @@ void Session::webpageApplyFields(
}
return nullptr;
};
const auto lookupThemeDocument = [&]() -> DocumentData* {
if (const auto attributes = data.vattributes()) {
for (const auto &attribute : attributes->v) {
@ -3582,6 +3588,7 @@ void Session::webpageApplyFields(
}
return nullptr;
};
using WebPageStickerSetPtr = std::unique_ptr<WebPageStickerSet>;
const auto lookupStickerSet = [&]() -> WebPageStickerSetPtr {
if (const auto attributes = data.vattributes()) {
@ -3605,6 +3612,21 @@ void Session::webpageApplyFields(
}
return nullptr;
};
using UniqueGiftPtr = std::shared_ptr<UniqueGift>;
const auto lookupUniqueGift = [&]() -> UniqueGiftPtr {
if (const auto attributes = data.vattributes()) {
for (const auto &attribute : attributes->v) {
return attribute.match([&](
const MTPDwebPageAttributeUniqueStarGift &data) {
const auto gift = Api::FromTL(_session, data.vgift());
return gift ? gift->unique : nullptr;
}, [](const auto &) -> UniqueGiftPtr { return nullptr; });
}
}
return nullptr;
};
auto story = (Data::Story*)nullptr;
auto storyId = FullStoryId();
if (const auto attributes = data.vattributes()) {
@ -3697,6 +3719,7 @@ void Session::webpageApplyFields(
WebPageCollage(this, data),
std::move(iv),
lookupStickerSet(),
lookupUniqueGift(),
data.vduration().value_or_empty(),
qs(data.vauthor().value_or_empty()),
data.is_has_large_media(),
@ -3717,6 +3740,7 @@ void Session::webpageApplyFields(
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration,
const QString &author,
bool hasLargeMedia,
@ -3735,6 +3759,7 @@ void Session::webpageApplyFields(
std::move(collage),
std::move(iv),
std::move(stickerSet),
std::move(uniqueGift),
duration,
author,
hasLargeMedia,

View file

@ -71,6 +71,7 @@ class BusinessInfo;
struct ReactionId;
struct UnavailableReason;
struct CreditsStatusSlice;
struct UniqueGift;
struct RepliesReadTillUpdate {
FullMsgId id;
@ -336,7 +337,7 @@ public:
void notifyPinnedDialogsOrderUpdated();
[[nodiscard]] rpl::producer<> pinnedDialogsOrderUpdated() const;
using CreditsSubsRebuilder = rpl::event_stream<Data::CreditsStatusSlice>;
using CreditsSubsRebuilder = rpl::event_stream<CreditsStatusSlice>;
using CreditsSubsRebuilderPtr = std::shared_ptr<CreditsSubsRebuilder>;
[[nodiscard]] CreditsSubsRebuilderPtr createCreditsSubsRebuilder();
[[nodiscard]] CreditsSubsRebuilderPtr activeCreditsSubsRebuilder() const;
@ -422,7 +423,7 @@ public:
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
FilterId filterId) const;
[[nodiscard]] const std::vector<Dialogs::Key> &pinnedChatsOrder(
not_null<Data::SavedMessages*> saved) const;
not_null<SavedMessages*> saved) const;
void setChatPinned(Dialogs::Key key, FilterId filterId, bool pinned);
void setPinnedFromEntryList(Dialogs::Key key, bool pinned);
void clearPinnedChats(Folder *folder);
@ -430,7 +431,7 @@ public:
Folder *folder,
const QVector<MTPDialogPeer> &list);
void applyPinnedTopics(
not_null<Data::Forum*> forum,
not_null<Forum*> forum,
const QVector<MTPint> &list);
void reorderTwoPinnedChats(
FilterId filterId,
@ -624,6 +625,7 @@ public:
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration,
const QString &author,
bool hasLargeMedia,
@ -908,6 +910,7 @@ private:
WebPageCollage &&collage,
std::unique_ptr<Iv::Data> iv,
std::unique_ptr<WebPageStickerSet> stickerSet,
std::shared_ptr<UniqueGift> uniqueGift,
int duration,
const QString &author,
bool hasLargeMedia,

View file

@ -224,6 +224,7 @@ bool WebPageData::applyChanges(
WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@ -278,6 +279,7 @@ bool WebPageData::applyChanges(
&& (!iv == !newIv)
&& (!iv || iv->partial() == newIv->partial())
&& (!stickerSet == !newStickerSet)
&& (!uniqueGift == !newUniqueGift)
&& duration == newDuration
&& author == resultAuthor
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
@ -300,6 +302,7 @@ bool WebPageData::applyChanges(
collage = std::move(newCollage);
iv = std::move(newIv);
stickerSet = std::move(newStickerSet);
uniqueGift = std::move(newUniqueGift);
duration = newDuration;
author = resultAuthor;
pendingTill = newPendingTill;

View file

@ -15,6 +15,7 @@ class ChannelData;
namespace Data {
class Session;
struct UniqueGift;
} // namespace Data
namespace Iv {
@ -101,6 +102,7 @@ struct WebPageData {
WebPageCollage &&newCollage,
std::unique_ptr<Iv::Data> newIv,
std::unique_ptr<WebPageStickerSet> newStickerSet,
std::shared_ptr<Data::UniqueGift> newUniqueGift,
int newDuration,
const QString &newAuthor,
bool newHasLargeMedia,
@ -129,6 +131,7 @@ struct WebPageData {
WebPageCollage collage;
std::unique_ptr<Iv::Data> iv;
std::unique_ptr<WebPageStickerSet> stickerSet;
std::shared_ptr<Data::UniqueGift> uniqueGift;
int duration = 0;
TimeId pendingTill = 0;
uint32 version : 30 = 0;

View file

@ -720,6 +720,7 @@ HistoryItem::HistoryItem(
WebPageCollage(),
nullptr,
nullptr,
nullptr,
0,
QString(),
false,

View file

@ -82,8 +82,12 @@ auto GenerateChatIntro(
Element *replacing,
const Data::ChatIntro &data,
Fn<void(not_null<DocumentData*>)> helloChosen)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
auto pushText = [&](
TextWithEntities text,
QMargins margins = {},

View file

@ -32,8 +32,12 @@ constexpr auto kOutlineRatio = 0.85;
auto GenerateGiveawayStart(
not_null<Element*> parent,
not_null<Data::GiveawayStart*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
-> 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 months = data->months;
const auto quantity = data->quantity;
@ -201,8 +205,12 @@ auto GenerateGiveawayStart(
auto GenerateGiveawayResults(
not_null<Element*> parent,
not_null<Data::GiveawayResults*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
-> 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 quantity = data->winnersCount;
using Data = StickerWithBadgePart::Data;

View file

@ -15,16 +15,21 @@ struct GiveawayResults;
namespace HistoryView {
class Element;
class MediaGeneric;
class MediaGenericPart;
[[nodiscard]] auto GenerateGiveawayStart(
not_null<Element*> parent,
not_null<Data::GiveawayStart*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] auto GenerateGiveawayResults(
not_null<Element*> parent,
not_null<Data::GiveawayResults*> data)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
} // namespace HistoryView

View file

@ -71,14 +71,16 @@ auto MediaGenericPart::stickerTakePlayer(
MediaGeneric::MediaGeneric(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<Part>)>)> generate,
MediaGenericDescriptor &&descriptor)
: Media(parent)
, _paintBg(std::move(descriptor.paintBg))
, _maxWidthCap(descriptor.maxWidth)
, _service(descriptor.service)
, _hideServiceText(descriptor.hideServiceText) {
generate([&](std::unique_ptr<Part> part) {
generate(this, [&](std::unique_ptr<Part> part) {
_entries.push_back({
.object = std::move(part),
});
@ -125,7 +127,7 @@ void MediaGeneric::draw(Painter &p, const PaintContext &context) const {
if (outer < st::msgPadding.left() + st::msgPadding.right() + 1) {
return;
} else if (_paintBg) {
_paintBg(p, context);
_paintBg(p, context, this);
} else if (_service) {
PainterHighQualityEnabler hq(p);
const auto radius = st::msgServiceGiftBoxRadius;

View file

@ -53,7 +53,10 @@ public:
struct MediaGenericDescriptor {
int maxWidth = 0;
Fn<void(Painter&, const PaintContext&)> paintBg;
Fn<void(
Painter&,
const PaintContext&,
not_null<const MediaGeneric*>)> paintBg;
ClickHandlerPtr serviceLink;
bool service = false;
bool hideServiceText = false;
@ -65,7 +68,9 @@ public:
MediaGeneric(
not_null<Element*> parent,
Fn<void(Fn<void(std::unique_ptr<Part>)>)> generate,
Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<Part>)>)> generate,
MediaGenericDescriptor &&descriptor = {});
~MediaGeneric();
@ -119,7 +124,10 @@ private:
[[nodiscard]] QMargins inBubblePadding() const;
std::vector<Entry> _entries;
Fn<void(Painter&, const PaintContext&)> _paintBg;
Fn<void(
Painter&,
const PaintContext&,
not_null<const MediaGeneric*>)> _paintBg;
int _maxWidthCap = 0;
bool _service : 1 = false;
bool _hideServiceText : 1 = false;
@ -150,7 +158,7 @@ public:
protected:
virtual void setupPen(
Painter &p,
Painter &p,
not_null<const MediaGeneric*> owner,
const PaintContext &context) const;

View file

@ -383,9 +383,13 @@ QSize AttributeTable::countCurrentSize(int newWidth) {
auto GenerateUniqueGiftMedia(
not_null<Element*> parent,
Element *replacing,
not_null<Data::UniqueGift*> gift)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
std::shared_ptr<Data::UniqueGift> gift)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
return [=](
not_null<MediaGeneric*> media,
Fn<void(std::unique_ptr<MediaGenericPart>)> push) {
auto pushText = [&](
TextWithEntities text,
const style::TextStyle &st,
@ -402,8 +406,8 @@ auto GenerateUniqueGiftMedia(
};
const auto item = parent->data();
const auto media = item->media();
const auto fields = media ? media->gift() : nullptr;
const auto itemMedia = item->media();
const auto fields = itemMedia ? itemMedia->gift() : nullptr;
const auto upgrade = fields && fields->upgrade;
const auto outgoing = upgrade ? !item->out() : item->out();
@ -478,9 +482,13 @@ auto GenerateUniqueGiftMedia(
};
}
Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
not_null<Element*> view,
not_null<Data::UniqueGift*> gift) {
auto UniqueGiftBg(
not_null<Element*> view,
std::shared_ptr<Data::UniqueGift> gift)
-> Fn<void(
Painter&,
const Ui::ChatPaintContext&,
not_null<const MediaGeneric*>)> {
struct State {
QImage bg;
base::flat_map<float64, QImage> cache;
@ -495,20 +503,25 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
Data::CustomEmojiSizeTag::Large);
[[maybe_unused]] const auto preload = state->pattern->ready();
return [=](Painter &p, const Ui::ChatPaintContext &context) {
return [=](
Painter &p,
const Ui::ChatPaintContext &context,
not_null<const MediaGeneric*> media) {
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
const auto thickness = st::chatUniqueGiftBorder * 2;
auto pen = context.st->msgServiceBg()->p;
pen.setWidthF(thickness);
p.setPen(pen);
p.setBrush(Qt::transparent);
const auto webpreview = (media.get() != view->media());
const auto thickness = webpreview ? 0 : st::chatUniqueGiftBorder * 2;
const auto radius = st::msgServiceGiftBoxRadius - thickness;
const auto media = view->media();
const auto full = QRect(0, 0, media->width(), media->height());
const auto inner = full.marginsRemoved(
{ thickness, thickness, thickness, thickness });
p.drawRoundedRect(inner, radius, radius);
if (!webpreview) {
auto pen = context.st->msgServiceBg()->p;
pen.setWidthF(thickness);
p.setPen(pen);
p.setBrush(Qt::transparent);
p.drawRoundedRect(inner, radius, radius);
}
auto gradient = QRadialGradient(
inner.center(),
inner.height() / 2);
@ -534,7 +547,7 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
outer);
p.setClipping(false);
const auto add = style::ConvertScale(2);
const auto add = webpreview ? 0 : style::ConvertScale(2);
p.setClipRect(
inner.x() - add,
inner.y() - add,
@ -560,6 +573,32 @@ Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
};
}
auto GenerateUniqueGiftPreview(
not_null<Element*> parent,
Element *replacing,
std::shared_ptr<Data::UniqueGift> gift)
-> 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 sticker = [=] {
using Tag = ChatHelpers::StickerLottieSize;
return StickerInBubblePart::Data{
.sticker = gift->model.document,
.size = st::chatIntroStickerSize,
.cacheTag = Tag::ChatIntroHelloSticker,
};
};
push(std::make_unique<StickerInBubblePart>(
parent,
replacing,
sticker,
st::chatUniquePreviewPadding));
};
}
std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,
QMargins margins,

View file

@ -21,17 +21,32 @@ struct ChatPaintContext;
namespace HistoryView {
class Element;
class MediaGeneric;
class MediaGenericPart;
auto GenerateUniqueGiftMedia(
[[nodiscard]] auto GenerateUniqueGiftMedia(
not_null<Element*> parent,
Element *replacing,
not_null<Data::UniqueGift*> gift)
-> Fn<void(Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
std::shared_ptr<Data::UniqueGift> gift)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] Fn<void(Painter&, const Ui::ChatPaintContext &)> UniqueGiftBg(
[[nodiscard]] auto UniqueGiftBg(
not_null<Element*> view,
not_null<Data::UniqueGift*> gift);
std::shared_ptr<Data::UniqueGift> gift)
-> Fn<void(
Painter&,
const Ui::ChatPaintContext&,
not_null<const MediaGeneric*>)>;
[[nodiscard]] auto GenerateUniqueGiftPreview(
not_null<Element*> parent,
Element *replacing,
std::shared_ptr<Data::UniqueGift> gift)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)>;
[[nodiscard]] std::unique_ptr<MediaGenericPart> MakeGenericButtonPart(
const QString &text,

View file

@ -21,7 +21,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_web_page.h"
#include "history/view/media/history_view_media_common.h"
#include "history/view/media/history_view_sticker.h"
#include "history/view/media/history_view_media_generic.h"
#include "history/view/media/history_view_unique_gift.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_message.h"
#include "history/view/history_view_reply.h"
@ -196,6 +197,8 @@ constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000);
const auto type = page->type;
const auto text = Ui::Text::Upper(page->iv
? tr::lng_view_button_iv(tr::now)
: page->uniqueGift
? tr::lng_view_button_collectible(tr::now)
: (type == WebPageType::Theme)
? tr::lng_view_button_theme(tr::now)
: (type == WebPageType::Story)
@ -245,6 +248,7 @@ constexpr auto kFactcheckAboutDuration = 5 * crl::time(1000);
[[nodiscard]] bool HasButton(not_null<WebPageData*> webpage) {
const auto type = webpage->type;
return webpage->iv
|| webpage->uniqueGift
|| (type == WebPageType::Message)
|| (type == WebPageType::Group)
|| (type == WebPageType::GroupWithRequest)
@ -501,7 +505,18 @@ QSize WebPage::countOptimalSize() {
}
// init attach
if (!_attach && !_asArticle) {
if (!_attach && _data->uniqueGift) {
_attach = std::make_unique<MediaGeneric>(
_parent,
GenerateUniqueGiftPreview(
_parent,
nullptr,
_data->uniqueGift),
MediaGenericDescriptor{
.maxWidth = st::msgServiceGiftPreview,
.paintBg = UniqueGiftBg(_parent, _data->uniqueGift),
});
} else if (!_attach && !_asArticle) {
_attach = CreateAttach(
_parent,
_data->document,
@ -511,7 +526,9 @@ QSize WebPage::countOptimalSize() {
}
// init strings
if (_description.isEmpty() && !_data->description.text.isEmpty()) {
if (_description.isEmpty()
&& !_data->description.text.isEmpty()
&& !_data->uniqueGift) {
const auto &text = _data->description;
if (isLogEntryOriginal()) {

View file

@ -911,6 +911,7 @@ searchInChatPeerList: PeerList(defaultPeerList) {
}
searchInChatTagsPadding: margins(6px, 0px, 6px, 0px);
msgServiceGiftPreview: 172px;
msgServiceGiftBoxSize: size(236px, 231px); // Plus msgServiceGiftBoxTopSkip.
msgServiceGiftBoxRadius: 20px;
msgServiceGiftBoxTopSkip: 4px;
@ -1211,6 +1212,7 @@ botDownloadCancel: IconButton {
chatUniqueGiftBorder: 4px;
chatUniqueStickerPadding: margins(10px, 30px, 10px, 9px);
chatUniquePreviewPadding: margins(10px, 30px, 10px, 32px);
chatUniqueTitle: TextStyle(defaultTextStyle) {
font: font(16px semibold);
}