Display topic name in chats list.

This commit is contained in:
John Preston 2022-11-02 16:39:13 +04:00
parent fdf4129e5e
commit 34a2c5c8ce
28 changed files with 223 additions and 172 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

View file

@ -2049,6 +2049,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_from_wrapped" = "{from}:";
"lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media" = "{media_part} {caption}";
"lng_dialogs_text_media_wrapped" = "{media},"; "lng_dialogs_text_media_wrapped" = "{media},";
"lng_dialogs_text_from_in_topic" = "{from} {topic}";
"lng_dialogs_show_all_chats" = "Show all chats"; "lng_dialogs_show_all_chats" = "Show all chats";
"lng_dialogs_hide_muted_chats" = "Hide muted chats"; "lng_dialogs_hide_muted_chats" = "Hide muted chats";
"lng_dialogs_skip_archive_in_search" = "Skip results from archive"; "lng_dialogs_skip_archive_in_search" = "Skip results from archive";

View file

@ -21,9 +21,7 @@ using namespace TextUtilities;
[[nodiscard]] QString CustomEmojiEntityData( [[nodiscard]] QString CustomEmojiEntityData(
const MTPDmessageEntityCustomEmoji &data) { const MTPDmessageEntityCustomEmoji &data) {
return Data::SerializeCustomEmojiId({ return Data::SerializeCustomEmojiId(data.vdocument_id().v);
.id = data.vdocument_id().v,
});
} }
[[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity( [[nodiscard]] std::optional<MTPMessageEntity> CustomEmojiEntity(
@ -31,13 +29,13 @@ using namespace TextUtilities;
MTPint length, MTPint length,
const QString &data) { const QString &data) {
const auto parsed = Data::ParseCustomEmojiData(data); const auto parsed = Data::ParseCustomEmojiData(data);
if (!parsed.id) { if (!parsed) {
return {}; return {};
} }
return MTP_messageEntityCustomEmoji( return MTP_messageEntityCustomEmoji(
offset, offset,
length, length,
MTP_long(parsed.id)); MTP_long(parsed));
} }
[[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity( [[nodiscard]] std::optional<MTPMessageEntity> MentionNameEntity(

View file

@ -87,11 +87,11 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
} else if (Ui::InputField::IsCustomEmojiLink(tag)) { } else if (Ui::InputField::IsCustomEmojiLink(tag)) {
const auto data = Ui::InputField::CustomEmojiEntityData(tag); const auto data = Ui::InputField::CustomEmojiEntityData(tag);
const auto emoji = Data::ParseCustomEmojiData(data); const auto emoji = Data::ParseCustomEmojiData(data);
if (!emoji.id) { if (!emoji) {
i = all.erase(i); i = all.erase(i);
continue; continue;
} else if (!_session->premium()) { } else if (!_session->premium()) {
const auto document = _session->data().document(emoji.id); const auto document = _session->data().document(emoji);
if (document->isPremiumEmoji()) { if (document->isPremiumEmoji()) {
if (!_allowPremiumEmoji if (!_allowPremiumEmoji
|| premiumSkipped || premiumSkipped

View file

@ -225,6 +225,13 @@ void Forum::applyReceivedTopics(
).first->second.get() ).first->second.get()
: i->second.get(); : i->second.get();
raw->applyTopic(data); raw->applyTopic(data);
if (creating) {
if (const auto last = _history->chatListMessage()
; last && last->topicRootId() == rootId) {
_history->lastItemDialogsView().itemInvalidated(last);
_history->updateChatListEntry();
}
}
if (callback) { if (callback) {
callback(raw); callback(raw);
} }

View file

@ -139,6 +139,14 @@ QImage ForumTopicIconFrame(
return background; return background;
} }
TextWithEntities ForumTopicIconWithTitle(
DocumentId iconId,
const QString &title) {
return iconId
? Data::SingleCustomEmoji(iconId).append(title)
: TextWithEntities{ title };
}
ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId) ForumTopic::ForumTopic(not_null<Forum*> forum, MsgId rootId)
: Thread(&forum->history()->owner(), Type::ForumTopic) : Thread(&forum->history()->owner(), Type::ForumTopic)
, _forum(forum) , _forum(forum)
@ -570,6 +578,10 @@ QString ForumTopic::title() const {
return _title; return _title;
} }
TextWithEntities ForumTopic::titleWithIcon() const {
return ForumTopicIconWithTitle(_iconId, _title);
}
void ForumTopic::applyTitle(const QString &title) { void ForumTopic::applyTitle(const QString &title) {
if (_title == title) { if (_title == title) {
return; return;

View file

@ -44,6 +44,9 @@ class Forum;
int32 colorId, int32 colorId,
const QString &title, const QString &title,
const style::ForumTopicIcon &st); const style::ForumTopicIcon &st);
[[nodiscard]] TextWithEntities ForumTopicIconWithTitle(
DocumentId iconId,
const QString &title);
class ForumTopic final : public Thread { class ForumTopic final : public Thread {
public: public:
@ -107,6 +110,7 @@ public:
[[nodiscard]] MsgId lastKnownServerMessageId() const; [[nodiscard]] MsgId lastKnownServerMessageId() const;
[[nodiscard]] QString title() const; [[nodiscard]] QString title() const;
[[nodiscard]] TextWithEntities titleWithIcon() const;
void applyTitle(const QString &title); void applyTitle(const QString &title);
[[nodiscard]] DocumentId iconId() const; [[nodiscard]] DocumentId iconId() const;
void applyIconId(DocumentId iconId); void applyIconId(DocumentId iconId);

View file

@ -15,9 +15,7 @@ QString ReactionEntityData(const ReactionId &id) {
if (id.empty()) { if (id.empty()) {
return {}; return {};
} else if (const auto custom = id.custom()) { } else if (const auto custom = id.custom()) {
return SerializeCustomEmojiId({ return SerializeCustomEmojiId(custom);
.id = custom,
});
} }
return u"default:"_q + id.emoji(); return u"default:"_q + id.emoji();
} }

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/stickers_lottie.h" #include "chat_helpers/stickers_lottie.h"
#include "ui/widgets/input_fields.h" #include "ui/widgets/input_fields.h"
#include "ui/text/text_custom_emoji.h" #include "ui/text/text_custom_emoji.h"
#include "ui/text/text_utilities.h"
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "apiwrap.h" #include "apiwrap.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -91,7 +92,7 @@ class CustomEmojiLoader final
public: public:
CustomEmojiLoader( CustomEmojiLoader(
not_null<Session*> owner, not_null<Session*> owner,
const CustomEmojiId id, DocumentId id,
SizeTag tag, SizeTag tag,
int sizeOverride); int sizeOverride);
CustomEmojiLoader( CustomEmojiLoader(
@ -144,7 +145,7 @@ private:
[[nodiscard]] static std::variant<Resolve, Lookup, Load> InitialState( [[nodiscard]] static std::variant<Resolve, Lookup, Load> InitialState(
not_null<Session*> owner, not_null<Session*> owner,
const CustomEmojiId &id); DocumentId id);
std::variant<Resolve, Lookup, Load> _state; std::variant<Resolve, Lookup, Load> _state;
ushort _sizeOverride = 0; ushort _sizeOverride = 0;
@ -154,7 +155,7 @@ private:
CustomEmojiLoader::CustomEmojiLoader( CustomEmojiLoader::CustomEmojiLoader(
not_null<Session*> owner, not_null<Session*> owner,
const CustomEmojiId id, DocumentId id,
SizeTag tag, SizeTag tag,
int sizeOverride) int sizeOverride)
: _state(InitialState(owner, id)) : _state(InitialState(owner, id))
@ -368,9 +369,9 @@ void CustomEmojiLoader::check() {
auto CustomEmojiLoader::InitialState( auto CustomEmojiLoader::InitialState(
not_null<Session*> owner, not_null<Session*> owner,
const CustomEmojiId &id) DocumentId id)
-> std::variant<Resolve, Lookup, Load> { -> std::variant<Resolve, Lookup, Load> {
const auto document = owner->document(id.id); const auto document = owner->document(id);
if (document->sticker()) { if (document->sticker()) {
return Lookup{ document }; return Lookup{ document };
} }
@ -504,8 +505,8 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
SizeTag tag, SizeTag tag,
int sizeOverride) { int sizeOverride) {
const auto parsed = ParseCustomEmojiData(data); const auto parsed = ParseCustomEmojiData(data);
return parsed.id return parsed
? create(parsed.id, std::move(update), tag, sizeOverride) ? create(parsed, std::move(update), tag, sizeOverride)
: nullptr; : nullptr;
} }
@ -532,7 +533,7 @@ std::unique_ptr<Ui::Text::CustomEmoji> CustomEmojiManager::create(
void CustomEmojiManager::resolve( void CustomEmojiManager::resolve(
QStringView data, QStringView data,
not_null<Listener*> listener) { not_null<Listener*> listener) {
resolve(ParseCustomEmojiData(data).id, listener); resolve(ParseCustomEmojiData(data), listener);
} }
void CustomEmojiManager::resolve( void CustomEmojiManager::resolve(
@ -619,7 +620,7 @@ auto CustomEmojiManager::createLoaderWithSetId(
) -> LoaderWithSetId { ) -> LoaderWithSetId {
auto result = std::make_unique<CustomEmojiLoader>( auto result = std::make_unique<CustomEmojiLoader>(
_owner, _owner,
CustomEmojiId{ .id = documentId }, documentId,
tag, tag,
sizeOverride); sizeOverride);
if (const auto document = result->document()) { if (const auto document = result->document()) {
@ -882,18 +883,24 @@ int FrameSizeFromTag(SizeTag tag) {
return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor; return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor;
} }
QString SerializeCustomEmojiId(const CustomEmojiId &id) { QString SerializeCustomEmojiId(DocumentId id) {
return QString::number(id.id); return QString::number(id);
} }
QString SerializeCustomEmojiId(not_null<DocumentData*> document) { QString SerializeCustomEmojiId(not_null<DocumentData*> document) {
return SerializeCustomEmojiId({ return SerializeCustomEmojiId(document->id);
.id = document->id,
});
} }
CustomEmojiId ParseCustomEmojiData(QStringView data) { DocumentId ParseCustomEmojiData(QStringView data) {
return { .id = data.toULongLong() }; return data.toULongLong();
}
TextWithEntities SingleCustomEmoji(DocumentId id) {
return Ui::Text::SingleCustomEmoji(SerializeCustomEmojiId(id));
}
TextWithEntities SingleCustomEmoji(not_null<DocumentData*> document) {
return SingleCustomEmoji(document->id);
} }
bool AllowEmojiWithoutPremium(not_null<PeerData*> peer) { bool AllowEmojiWithoutPremium(not_null<PeerData*> peer) {

View file

@ -21,10 +21,6 @@ namespace Data {
class Session; class Session;
class CustomEmojiLoader; class CustomEmojiLoader;
struct CustomEmojiId {
DocumentId id = 0;
};
enum class CustomEmojiSizeTag : uchar { enum class CustomEmojiSizeTag : uchar {
Normal, Normal,
Large, Large,
@ -176,10 +172,14 @@ private:
[[nodiscard]] int FrameSizeFromTag(CustomEmojiManager::SizeTag tag); [[nodiscard]] int FrameSizeFromTag(CustomEmojiManager::SizeTag tag);
[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id); [[nodiscard]] QString SerializeCustomEmojiId(DocumentId id);
[[nodiscard]] QString SerializeCustomEmojiId( [[nodiscard]] QString SerializeCustomEmojiId(
not_null<DocumentData*> document); not_null<DocumentData*> document);
[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data); [[nodiscard]] DocumentId ParseCustomEmojiData(QStringView data);
[[nodiscard]] TextWithEntities SingleCustomEmoji(DocumentId id);
[[nodiscard]] TextWithEntities SingleCustomEmoji(
not_null<DocumentData*> document);
[[nodiscard]] bool AllowEmojiWithoutPremium(not_null<PeerData*> peer); [[nodiscard]] bool AllowEmojiWithoutPremium(not_null<PeerData*> peer);

View file

@ -69,7 +69,7 @@ defaultDialogRow: DialogRow {
nameLeft: 68px; nameLeft: 68px;
nameTop: 10px; nameTop: 10px;
textLeft: 68px; textLeft: 68px;
textTop: 35px; textTop: 34px;
} }
dialogsOnlineBadgeStroke: 2px; dialogsOnlineBadgeStroke: 2px;
@ -460,3 +460,7 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) {
chooseTopicList: PeerList(defaultPeerList) { chooseTopicList: PeerList(defaultPeerList) {
item: chooseTopicListItem; item: chooseTopicListItem;
} }
dialogsTopicArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgService }};
dialogsTopicArrowSkip: 13px;
dialogsTopicArrowTop: 4px;

View file

@ -921,7 +921,7 @@ void RowPainter::Paint(
view->prepare( view->prepare(
item, item,
[=] { entry->updateChatListEntry(); }, [=] { entry->updateChatListEntry(); },
{}); { .ignoreTopic = (!history || !peer->isForum()) });
} }
view->paint(p, rect, context); view->paint(p, rect, context);
} }

View file

@ -18,19 +18,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h" #include "ui/painter.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lang/lang_text_entity.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
namespace { namespace {
template <ushort kTag> template <ushort kTag>
struct TextWithTagOffset { struct TextWithTagOffset {
TextWithTagOffset(QString text) : text(text) { TextWithTagOffset(TextWithEntities text) : text(std::move(text)) {
}
TextWithTagOffset(QString text) : text({ std::move(text) }) {
} }
static TextWithTagOffset FromString(const QString &text) { static TextWithTagOffset FromString(const QString &text) {
return { text }; return { { text } };
} }
QString text; TextWithEntities text;
int offset = -1; int offset = -1;
}; };
@ -52,12 +55,12 @@ TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
ushort tag, ushort tag,
const TextWithTagOffset<kTag> &replacement) { const TextWithTagOffset<kTag> &replacement) {
const auto replacementPosition = FindTagReplacementPosition( const auto replacementPosition = FindTagReplacementPosition(
original.text, original.text.text,
tag); tag);
if (replacementPosition < 0) { if (replacementPosition < 0) {
return std::move(original); return std::move(original);
} }
original.text = ReplaceTag<QString>::Replace( original.text = ReplaceTag<TextWithEntities>::Replace(
std::move(original.text), std::move(original.text),
replacement.text, replacement.text,
replacementPosition); replacementPosition);
@ -65,7 +68,8 @@ TextWithTagOffset<kTag> ReplaceTag<TextWithTagOffset<kTag>>::Call(
original.offset = replacementPosition; original.offset = replacementPosition;
} else if (original.offset > replacementPosition) { } else if (original.offset > replacementPosition) {
constexpr auto kReplaceCommandLength = 4; constexpr auto kReplaceCommandLength = 4;
original.offset += replacement.text.size() - kReplaceCommandLength; const auto replacementSize = replacement.text.text.size();
original.offset += replacementSize - kReplaceCommandLength;
} }
return std::move(original); return std::move(original);
} }
@ -105,6 +109,7 @@ struct MessageView::LoadingContext {
MessageView::MessageView() MessageView::MessageView()
: _senderCache(st::dialogsTextWidthMin) : _senderCache(st::dialogsTextWidthMin)
, _topicCache(st::dialogsTextWidthMin)
, _textCache(st::dialogsTextWidthMin) { , _textCache(st::dialogsTextWidthMin) {
} }
@ -130,28 +135,46 @@ void MessageView::prepare(
ToPreviewOptions options) { ToPreviewOptions options) {
options.existing = &_imagesCache; options.existing = &_imagesCache;
auto preview = item->toPreview(options); auto preview = item->toPreview(options);
if (!preview.images.empty() && preview.imagesInTextPosition > 0) { const auto hasImages = !preview.images.empty();
auto sender = ::Ui::Text::Mid( const auto hasArrow = (preview.arrowInTextPosition > 0)
preview.text, && (preview.imagesInTextPosition > preview.arrowInTextPosition);
0,
preview.imagesInTextPosition);
TextUtilities::Trim(sender);
_senderCache.setMarkedText(
st::dialogsTextStyle,
std::move(sender),
DialogTextOptions());
preview.text = ::Ui::Text::Mid(
preview.text,
preview.imagesInTextPosition);
} else {
_senderCache = { st::dialogsTextWidthMin };
}
TextUtilities::Trim(preview.text);
const auto history = item->history(); const auto history = item->history();
const auto context = Core::MarkedTextContext{ const auto context = Core::MarkedTextContext{
.session = &history->session(), .session = &history->session(),
.customEmojiRepaint = customEmojiRepaint, .customEmojiRepaint = customEmojiRepaint,
}; };
const auto senderTill = (preview.arrowInTextPosition > 0)
? preview.arrowInTextPosition
: preview.imagesInTextPosition;
if ((hasImages || hasArrow) && senderTill > 0) {
auto sender = Text::Mid(preview.text, 0, senderTill);
TextUtilities::Trim(sender);
_senderCache.setMarkedText(
st::dialogsTextStyle,
std::move(sender),
DialogTextOptions());
const auto topicTill = preview.imagesInTextPosition;
if (hasArrow && hasImages) {
auto topic = Text::Mid(
preview.text,
senderTill,
topicTill - senderTill);
TextUtilities::Trim(topic);
_topicCache.setMarkedText(
st::dialogsTextStyle,
std::move(topic),
DialogTextOptions(),
context);
preview.text = Text::Mid(preview.text, topicTill);
} else {
preview.text = Text::Mid(preview.text, senderTill);
_topicCache = { st::dialogsTextWidthMin };
}
} else {
_topicCache = { st::dialogsTextWidthMin };
_senderCache = { st::dialogsTextWidthMin };
}
TextUtilities::Trim(preview.text);
_textCache.setMarkedText( _textCache.setMarkedText(
st::dialogsTextStyle, st::dialogsTextStyle,
DialogsPreviewText(std::move(preview.text)), DialogsPreviewText(std::move(preview.text)),
@ -193,16 +216,46 @@ void MessageView::paint(
: st::dialogsTextPalette); : st::dialogsTextPalette);
auto rect = geometry; auto rect = geometry;
const auto lines = rect.height() / st::dialogsTextFont->height;
if (!_senderCache.isEmpty()) { if (!_senderCache.isEmpty()) {
_senderCache.draw(p, { _senderCache.draw(p, {
.position = rect.topLeft(), .position = rect.topLeft(),
.availableWidth = rect.width(), .availableWidth = rect.width(),
.palette = palette, .palette = palette,
.elisionLines = rect.height() / st::dialogsTextFont->height, .elisionLines = lines,
}); });
const auto skip = st::dialogsMiniPreviewSkip rect.setLeft(rect.x() + _senderCache.maxWidth());
+ st::dialogsMiniPreviewRight; if (!_topicCache.isEmpty() || _imagesCache.empty()) {
rect.setLeft(rect.x() + _senderCache.maxWidth() + skip); const auto skip = st::dialogsTopicArrowSkip;
if (rect.width() >= skip) {
const auto &icon = st::dialogsTopicArrow;
icon.paint(
p,
rect.x() + (skip - icon.width()) / 2,
rect.y() + st::dialogsTopicArrowTop,
geometry.width());
}
rect.setLeft(rect.x() + skip);
}
if (!_topicCache.isEmpty()) {
if (!rect.isEmpty()) {
_topicCache.draw(p, {
.position = rect.topLeft(),
.availableWidth = rect.width(),
.palette = palette,
.spoiler = Text::DefaultSpoilerCache(),
.now = context.now,
.paused = context.paused,
.elisionLines = lines,
});
}
rect.setLeft(rect.x() + _topicCache.maxWidth());
}
if (!_imagesCache.empty()) {
const auto skip = st::dialogsMiniPreviewSkip
+ st::dialogsMiniPreviewRight;
rect.setLeft(rect.x() + skip);
}
} }
for (const auto &image : _imagesCache) { for (const auto &image : _imagesCache) {
if (rect.width() < st::dialogsMiniPreview) { if (rect.width() < st::dialogsMiniPreview) {
@ -229,30 +282,48 @@ void MessageView::paint(
.spoiler = Text::DefaultSpoilerCache(), .spoiler = Text::DefaultSpoilerCache(),
.now = context.now, .now = context.now,
.paused = context.paused, .paused = context.paused,
.elisionLines = rect.height() / st::dialogsTextFont->height, .elisionLines = lines,
}); });
} }
HistoryView::ItemPreview PreviewWithSender( HistoryView::ItemPreview PreviewWithSender(
HistoryView::ItemPreview &&preview, HistoryView::ItemPreview &&preview,
const TextWithEntities &sender) { const QString &sender,
const auto textWithOffset = tr::lng_dialogs_text_with_from( TextWithEntities topic) {
auto senderWithOffset = topic.empty()
? TextWithTagOffset<lt_from>::FromString(sender)
: tr::lng_dialogs_text_from_in_topic(
tr::now,
lt_from,
{ sender },
lt_topic,
std::move(topic),
TextWithTagOffset<lt_from>::FromString);
auto wrappedWithOffset = tr::lng_dialogs_text_from_wrapped(
tr::now,
lt_from,
std::move(senderWithOffset.text),
TextWithTagOffset<lt_from>::FromString);
const auto wrappedSize = wrappedWithOffset.text.text.size();
auto fullWithOffset = tr::lng_dialogs_text_with_from(
tr::now, tr::now,
lt_from_part, lt_from_part,
sender.text, Ui::Text::PlainLink(std::move(wrappedWithOffset.text)),
lt_message,
preview.text.text,
TextWithTagOffset<lt_from_part>::FromString);
preview.text = tr::lng_dialogs_text_with_from(
tr::now,
lt_from_part,
sender,
lt_message, lt_message,
std::move(preview.text), std::move(preview.text),
Ui::Text::WithEntities); TextWithTagOffset<lt_from_part>::FromString);
preview.imagesInTextPosition = (textWithOffset.offset < 0) preview.text = std::move(fullWithOffset.text);
preview.arrowInTextPosition = (fullWithOffset.offset < 0
|| wrappedWithOffset.offset < 0
|| senderWithOffset.offset < 0)
? -1
: (fullWithOffset.offset
+ wrappedWithOffset.offset
+ senderWithOffset.offset
+ sender.size());
preview.imagesInTextPosition = (fullWithOffset.offset < 0)
? 0 ? 0
: textWithOffset.offset + sender.text.size(); : (fullWithOffset.offset + wrappedSize);
return std::move(preview); return std::move(preview);
} }

View file

@ -57,6 +57,7 @@ private:
mutable const HistoryItem *_textCachedFor = nullptr; mutable const HistoryItem *_textCachedFor = nullptr;
mutable Text::String _senderCache; mutable Text::String _senderCache;
mutable Text::String _topicCache;
mutable Text::String _textCache; mutable Text::String _textCache;
mutable std::vector<ItemPreviewImage> _imagesCache; mutable std::vector<ItemPreviewImage> _imagesCache;
mutable std::unique_ptr<LoadingContext> _loadingContext; mutable std::unique_ptr<LoadingContext> _loadingContext;
@ -65,6 +66,7 @@ private:
[[nodiscard]] HistoryView::ItemPreview PreviewWithSender( [[nodiscard]] HistoryView::ItemPreview PreviewWithSender(
HistoryView::ItemPreview &&preview, HistoryView::ItemPreview &&preview,
const TextWithEntities &sender); const QString &sender,
TextWithEntities topic);
} // namespace Dialogs::Ui } // namespace Dialogs::Ui

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h" #include "api/api_text_entities.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_forum_topic.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_message_reaction_id.h" #include "data/data_message_reaction_id.h"
@ -624,23 +625,10 @@ TextWithEntities GenerateDefaultBannedRightsChangeText(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
const MTPForumTopic &topic) { const MTPForumTopic &topic) {
return topic.match([&](const MTPDforumTopic &data) { return topic.match([&](const MTPDforumTopic &data) {
const auto wrapIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
auto result = (data.vicon_emoji_id() && data.vicon_emoji_id()->v)
? wrapIcon(data.vicon_emoji_id()->v)
: TextWithEntities();
result.append(qs(data.vtitle()));
return Ui::Text::Link( return Ui::Text::Link(
std::move(result), Data::ForumTopicIconWithTitle(
data.vicon_emoji_id().value_or_empty(),
qs(data.vtitle())),
u"internal:url:https://t.me/c/%1/%2"_q.arg( u"internal:url:https://t.me/c/%1/%2"_q.arg(
peerToChannel(channel->id).bare).arg( peerToChannel(channel->id).bare).arg(
data.vid().v)); data.vid().v));

View file

@ -1365,9 +1365,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
if (!sender) { if (!sender) {
return result; return result;
} }
const auto fromWrapped = Ui::Text::PlainLink( const auto topic = options.ignoreTopic ? nullptr : this->topic();
tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, *sender)); return Dialogs::Ui::PreviewWithSender(
return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped); std::move(result),
*sender,
topic ? topic->titleWithIcon() : TextWithEntities());
} }
TextWithEntities HistoryItem::inReplyText() const { TextWithEntities HistoryItem::inReplyText() const {

View file

@ -636,33 +636,19 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
return result; return result;
}; };
const auto wrapTopicIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) {
auto result = PreparedText{}; auto result = PreparedText{};
auto title = TextWithEntities{
qs(action.vtitle())
};
if (const auto icon = action.vicon_emoji_id().value_or_empty()) {
title = wrapTopicIcon(icon).append(' ').append(std::move(title));
}
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
.arg(peerToChannel(history()->peer->id).bare) .arg(peerToChannel(history()->peer->id).bare)
.arg(id.bare); .arg(id.bare);
result.text = tr::lng_action_topic_created( result.text = tr::lng_action_topic_created(
tr::now, tr::now,
lt_topic, lt_topic,
Ui::Text::Link(std::move(title), topicUrl), Ui::Text::Link(
Data::ForumTopicIconWithTitle(
action.vicon_emoji_id().value_or_empty(),
qs(action.vtitle())),
topicUrl),
Ui::Text::WithEntities); Ui::Text::WithEntities);
return result; return result;
}; };
@ -684,7 +670,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
lt_link, lt_link,
{ tr::lng_action_topic_placeholder(tr::now) }, { tr::lng_action_topic_placeholder(tr::now) },
lt_emoji, lt_emoji,
wrapTopicIcon(iconId), Data::SingleCustomEmoji(iconId),
Ui::Text::WithEntities); Ui::Text::WithEntities);
} else { } else {
result.links.push_back(fromLink()); result.links.push_back(fromLink());
@ -699,14 +685,6 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
} }
} else { } else {
result.links.push_back(fromLink()); result.links.push_back(fromLink());
auto title = TextWithEntities{
qs(*action.vtitle())
};
if (const auto icon = action.vicon_emoji_id().value_or_empty()) {
title = wrapTopicIcon(icon)
.append(' ')
.append(std::move(title));
}
result.text = tr::lng_action_topic_renamed( result.text = tr::lng_action_topic_renamed(
tr::now, tr::now,
lt_from, lt_from,
@ -714,7 +692,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) {
lt_link, lt_link,
{ tr::lng_action_topic_placeholder(tr::now) }, { tr::lng_action_topic_placeholder(tr::now) },
lt_title, lt_title,
std::move(title), Data::ForumTopicIconWithTitle(
action.vicon_emoji_id().value_or_empty(),
qs(*action.vtitle())),
Ui::Text::WithEntities); Ui::Text::WithEntities);
} }
if (result.text.empty()) { if (result.text.empty()) {

View file

@ -1311,7 +1311,7 @@ std::vector<StickerSetIdentifier> CollectEmojiPacks(
for (const auto &entity : item->originalText().entities) { for (const auto &entity : item->originalText().entities) {
if (entity.type() == EntityType::CustomEmoji) { if (entity.type() == EntityType::CustomEmoji) {
const auto data = Data::ParseCustomEmojiData(entity.data()); const auto data = Data::ParseCustomEmojiData(entity.data());
push(data.id); push(data);
} }
} }
break; break;

View file

@ -690,17 +690,6 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
return {}; return {};
} }
const auto from = item->from(); const auto from = item->from();
const auto wrapIcon = [](DocumentId id) {
return TextWithEntities{
"@",
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({ .id = id }))
},
};
};
const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q const auto topicUrl = u"internal:url:https://t.me/c/%1/%2"_q
.arg(peerToChannel(peerId).bare) .arg(peerToChannel(peerId).bare)
.arg(topicRootId.bare); .arg(topicRootId.bare);
@ -715,11 +704,9 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
const auto wrapTopic = [&]( const auto wrapTopic = [&](
const QString &title, const QString &title,
std::optional<DocumentId> iconId) { std::optional<DocumentId> iconId) {
auto result = TextWithEntities{ title }; return Ui::Text::Link(
auto full = (iconId && *iconId) Data::ForumTopicIconWithTitle(iconId.value_or(0), title),
? wrapIcon(*iconId).append(' ').append(std::move(result)) topicUrl);
: result;
return Ui::Text::Link(std::move(full), topicUrl);
}; };
const auto wrapParentTopic = [&] { const auto wrapParentTopic = [&] {
const auto forum = history()->asForum(); const auto forum = history()->asForum();
@ -783,7 +770,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks {
lt_link, lt_link,
placeholderLink(), placeholderLink(),
lt_emoji, lt_emoji,
wrapIcon(iconId), Data::SingleCustomEmoji(iconId),
Ui::Text::WithEntities), Ui::Text::WithEntities),
{ from->createOpenLink() }, { from->createOpenLink() },
}; };

View file

@ -21,6 +21,7 @@ struct ItemPreviewImage {
struct ItemPreview { struct ItemPreview {
TextWithEntities text; TextWithEntities text;
std::vector<ItemPreviewImage> images; std::vector<ItemPreviewImage> images;
int arrowInTextPosition = -1;
int imagesInTextPosition = 0; int imagesInTextPosition = 0;
std::any loadingContext; std::any loadingContext;
}; };
@ -31,7 +32,7 @@ struct ToPreviewOptions {
bool hideCaption = false; bool hideCaption = false;
bool generateImages = true; bool generateImages = true;
bool ignoreGroup = false; bool ignoreGroup = false;
bool ignoreSpoilers = false; bool ignoreTopic = true;
}; };
} // namespace HistoryView } // namespace HistoryView

View file

@ -92,7 +92,7 @@ CustomEmoji::CustomEmoji(
tag)); tag));
} else { } else {
const auto &data = element.entityData; const auto &data = element.entityData;
const auto id = Data::ParseCustomEmojiData(data).id; const auto id = Data::ParseCustomEmojiData(data);
const auto document = owner->document(id); const auto document = owner->document(id);
if (document->sticker()) { if (document->sticker()) {
_lines.back().push_back(createStickerPart(document)); _lines.back().push_back(createStickerPart(document));

View file

@ -16,25 +16,28 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
if (replacementPosition < 0) { if (replacementPosition < 0) {
return std::move(original); return std::move(original);
} }
return Replace(std::move(original), replacement, replacementPosition);
}
TextWithEntities ReplaceTag<TextWithEntities>::Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start) {
auto result = TextWithEntities(); auto result = TextWithEntities();
result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, replacementPosition); result.text = ReplaceTag<QString>::Replace(std::move(original.text), replacement.text, start);
auto originalEntitiesCount = original.entities.size(); auto originalEntitiesCount = original.entities.size();
auto replacementEntitiesCount = replacement.entities.size(); auto replacementEntitiesCount = replacement.entities.size();
if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) { if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) {
result.entities.reserve(originalEntitiesCount + replacementEntitiesCount); result.entities.reserve(originalEntitiesCount + replacementEntitiesCount);
auto replacementEnd = replacementPosition + int(replacement.text.size()); auto replacementEnd = start + int(replacement.text.size());
auto replacementEntity = replacement.entities.cbegin(); auto replacementEntity = replacement.entities.cbegin();
auto addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) { auto addReplacementEntitiesUntil = [&](int untilPosition) {
while (replacementEntity != replacement.entities.cend()) { while (replacementEntity != replacement.entities.cend()) {
auto newOffset = replacementPosition + replacementEntity->offset(); auto newOffset = start + replacementEntity->offset();
if (newOffset >= untilPosition) { if (newOffset >= untilPosition) {
return; return;
} }
auto newEnd = newOffset + replacementEntity->length(); auto newEnd = newOffset + replacementEntity->length();
newOffset = std::clamp(newOffset, replacementPosition, replacementEnd); newOffset = std::clamp(newOffset, start, replacementEnd);
newEnd = std::clamp(newEnd, replacementPosition, replacementEnd); newEnd = std::clamp(newEnd, start, replacementEnd);
if (auto newLength = newEnd - newOffset) { if (auto newLength = newEnd - newOffset) {
result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() }); result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() });
} }
@ -46,10 +49,10 @@ TextWithEntities ReplaceTag<TextWithEntities>::Call(TextWithEntities &&original,
// Transform the entity by the replacement. // Transform the entity by the replacement.
auto offset = entity.offset(); auto offset = entity.offset();
auto end = offset + entity.length(); auto end = offset + entity.length();
if (offset > replacementPosition) { if (offset > start) {
offset = offset + replacement.text.size() - kTagReplacementSize; offset = offset + replacement.text.size() - kTagReplacementSize;
} }
if (end > replacementPosition) { if (end > start) {
end = end + replacement.text.size() - kTagReplacementSize; end = end + replacement.text.size() - kTagReplacementSize;
} }
offset = std::clamp(offset, 0, int(result.text.size())); offset = std::clamp(offset, 0, int(result.text.size()));

View file

@ -27,6 +27,7 @@ struct ReplaceTag;
template <> template <>
struct ReplaceTag<TextWithEntities> { struct ReplaceTag<TextWithEntities> {
static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement); static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement);
static TextWithEntities Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start);
}; };

View file

@ -848,7 +848,7 @@ TextWithEntities Manager::ComposeReactionEmoji(
EntityType::CustomEmoji, EntityType::CustomEmoji,
0, 0,
text.size(), text.size(),
Data::SerializeCustomEmojiId(Data::CustomEmojiId{ id })) Data::SerializeCustomEmojiId(id))
} }
}; };
} }

View file

@ -968,24 +968,9 @@ void Notification::updateNotifyDisplay() {
const auto topicWithChat = [&]() -> TextWithEntities { const auto topicWithChat = [&]() -> TextWithEntities {
const auto name = _history->peer->name(); const auto name = _history->peer->name();
const auto wrapIcon = [](DocumentId id) { return _topic
return TextWithEntities{ ? _topic->titleWithIcon().append(u" ("_q + name + ')')
"@", : TextWithEntities{ name };
{ EntityInText(
EntityType::CustomEmoji,
0,
1,
Data::SerializeCustomEmojiId({.id = id }))
},
};
};
if (!_topic) {
return { name };
}
auto start = _topic->iconId()
? wrapIcon(_topic->iconId())
: TextWithEntities();
return start.append(_topic->title() + u" ("_q + name + ')');
}; };
auto title = options.hideNameAndPhoto auto title = options.hideNameAndPhoto
? TextWithEntities{ u"Telegram Desktop"_q } ? TextWithEntities{ u"Telegram Desktop"_q }

@ -1 +1 @@
Subproject commit c3616927ebfcbe98375189be87821bea202d9587 Subproject commit 4539d0bab4ffc335de79b77ee9632f6a7b59d48b