diff --git a/Telegram/Resources/icons/dialogs/dialogs_topic_arrow.png b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow.png new file mode 100644 index 000000000..a998c1ad1 Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@2x.png b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@2x.png new file mode 100644 index 000000000..16b43d5ff Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@2x.png differ diff --git a/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@3x.png b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@3x.png new file mode 100644 index 000000000..f8e31f59e Binary files /dev/null and b/Telegram/Resources/icons/dialogs/dialogs_topic_arrow@3x.png differ diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index dcfa8a058..5b3f602fb 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2049,6 +2049,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_dialogs_text_from_wrapped" = "{from}:"; "lng_dialogs_text_media" = "{media_part} {caption}"; "lng_dialogs_text_media_wrapped" = "{media},"; +"lng_dialogs_text_from_in_topic" = "{from} {topic}"; "lng_dialogs_show_all_chats" = "Show all chats"; "lng_dialogs_hide_muted_chats" = "Hide muted chats"; "lng_dialogs_skip_archive_in_search" = "Skip results from archive"; diff --git a/Telegram/SourceFiles/api/api_text_entities.cpp b/Telegram/SourceFiles/api/api_text_entities.cpp index a782d6153..1bfe1142f 100644 --- a/Telegram/SourceFiles/api/api_text_entities.cpp +++ b/Telegram/SourceFiles/api/api_text_entities.cpp @@ -21,9 +21,7 @@ using namespace TextUtilities; [[nodiscard]] QString CustomEmojiEntityData( const MTPDmessageEntityCustomEmoji &data) { - return Data::SerializeCustomEmojiId({ - .id = data.vdocument_id().v, - }); + return Data::SerializeCustomEmojiId(data.vdocument_id().v); } [[nodiscard]] std::optional CustomEmojiEntity( @@ -31,13 +29,13 @@ using namespace TextUtilities; MTPint length, const QString &data) { const auto parsed = Data::ParseCustomEmojiData(data); - if (!parsed.id) { + if (!parsed) { return {}; } return MTP_messageEntityCustomEmoji( offset, length, - MTP_long(parsed.id)); + MTP_long(parsed)); } [[nodiscard]] std::optional MentionNameEntity( diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 6d9aa9f9d..30ee76754 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -87,11 +87,11 @@ QString FieldTagMimeProcessor::operator()(QStringView mimeTag) { } else if (Ui::InputField::IsCustomEmojiLink(tag)) { const auto data = Ui::InputField::CustomEmojiEntityData(tag); const auto emoji = Data::ParseCustomEmojiData(data); - if (!emoji.id) { + if (!emoji) { i = all.erase(i); continue; } else if (!_session->premium()) { - const auto document = _session->data().document(emoji.id); + const auto document = _session->data().document(emoji); if (document->isPremiumEmoji()) { if (!_allowPremiumEmoji || premiumSkipped diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index b202fd099..cc9e6272b 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -225,6 +225,13 @@ void Forum::applyReceivedTopics( ).first->second.get() : i->second.get(); raw->applyTopic(data); + if (creating) { + if (const auto last = _history->chatListMessage() + ; last && last->topicRootId() == rootId) { + _history->lastItemDialogsView().itemInvalidated(last); + _history->updateChatListEntry(); + } + } if (callback) { callback(raw); } diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index bf1abed3d..d7979f2ac 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -139,6 +139,14 @@ QImage ForumTopicIconFrame( return background; } +TextWithEntities ForumTopicIconWithTitle( + DocumentId iconId, + const QString &title) { + return iconId + ? Data::SingleCustomEmoji(iconId).append(title) + : TextWithEntities{ title }; +} + ForumTopic::ForumTopic(not_null forum, MsgId rootId) : Thread(&forum->history()->owner(), Type::ForumTopic) , _forum(forum) @@ -570,6 +578,10 @@ QString ForumTopic::title() const { return _title; } +TextWithEntities ForumTopic::titleWithIcon() const { + return ForumTopicIconWithTitle(_iconId, _title); +} + void ForumTopic::applyTitle(const QString &title) { if (_title == title) { return; diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 14cab3434..4be29cdc5 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -44,6 +44,9 @@ class Forum; int32 colorId, const QString &title, const style::ForumTopicIcon &st); +[[nodiscard]] TextWithEntities ForumTopicIconWithTitle( + DocumentId iconId, + const QString &title); class ForumTopic final : public Thread { public: @@ -107,6 +110,7 @@ public: [[nodiscard]] MsgId lastKnownServerMessageId() const; [[nodiscard]] QString title() const; + [[nodiscard]] TextWithEntities titleWithIcon() const; void applyTitle(const QString &title); [[nodiscard]] DocumentId iconId() const; void applyIconId(DocumentId iconId); diff --git a/Telegram/SourceFiles/data/data_message_reaction_id.cpp b/Telegram/SourceFiles/data/data_message_reaction_id.cpp index c5f4670b3..1103d2e15 100644 --- a/Telegram/SourceFiles/data/data_message_reaction_id.cpp +++ b/Telegram/SourceFiles/data/data_message_reaction_id.cpp @@ -15,9 +15,7 @@ QString ReactionEntityData(const ReactionId &id) { if (id.empty()) { return {}; } else if (const auto custom = id.custom()) { - return SerializeCustomEmojiId({ - .id = custom, - }); + return SerializeCustomEmojiId(custom); } return u"default:"_q + id.emoji(); } diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp index 7a4cdb7ad..c1b047d53 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.cpp @@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "chat_helpers/stickers_lottie.h" #include "ui/widgets/input_fields.h" #include "ui/text/text_custom_emoji.h" +#include "ui/text/text_utilities.h" #include "ui/ui_utility.h" #include "apiwrap.h" #include "styles/style_chat.h" @@ -91,7 +92,7 @@ class CustomEmojiLoader final public: CustomEmojiLoader( not_null owner, - const CustomEmojiId id, + DocumentId id, SizeTag tag, int sizeOverride); CustomEmojiLoader( @@ -144,7 +145,7 @@ private: [[nodiscard]] static std::variant InitialState( not_null owner, - const CustomEmojiId &id); + DocumentId id); std::variant _state; ushort _sizeOverride = 0; @@ -154,7 +155,7 @@ private: CustomEmojiLoader::CustomEmojiLoader( not_null owner, - const CustomEmojiId id, + DocumentId id, SizeTag tag, int sizeOverride) : _state(InitialState(owner, id)) @@ -368,9 +369,9 @@ void CustomEmojiLoader::check() { auto CustomEmojiLoader::InitialState( not_null owner, - const CustomEmojiId &id) + DocumentId id) -> std::variant { - const auto document = owner->document(id.id); + const auto document = owner->document(id); if (document->sticker()) { return Lookup{ document }; } @@ -504,8 +505,8 @@ std::unique_ptr CustomEmojiManager::create( SizeTag tag, int sizeOverride) { const auto parsed = ParseCustomEmojiData(data); - return parsed.id - ? create(parsed.id, std::move(update), tag, sizeOverride) + return parsed + ? create(parsed, std::move(update), tag, sizeOverride) : nullptr; } @@ -532,7 +533,7 @@ std::unique_ptr CustomEmojiManager::create( void CustomEmojiManager::resolve( QStringView data, not_null listener) { - resolve(ParseCustomEmojiData(data).id, listener); + resolve(ParseCustomEmojiData(data), listener); } void CustomEmojiManager::resolve( @@ -619,7 +620,7 @@ auto CustomEmojiManager::createLoaderWithSetId( ) -> LoaderWithSetId { auto result = std::make_unique( _owner, - CustomEmojiId{ .id = documentId }, + documentId, tag, sizeOverride); if (const auto document = result->document()) { @@ -882,18 +883,24 @@ int FrameSizeFromTag(SizeTag tag) { return Ui::Text::AdjustCustomEmojiSize(emoji / factor) * factor; } -QString SerializeCustomEmojiId(const CustomEmojiId &id) { - return QString::number(id.id); +QString SerializeCustomEmojiId(DocumentId id) { + return QString::number(id); } QString SerializeCustomEmojiId(not_null document) { - return SerializeCustomEmojiId({ - .id = document->id, - }); + return SerializeCustomEmojiId(document->id); } -CustomEmojiId ParseCustomEmojiData(QStringView data) { - return { .id = data.toULongLong() }; +DocumentId ParseCustomEmojiData(QStringView data) { + return data.toULongLong(); +} + +TextWithEntities SingleCustomEmoji(DocumentId id) { + return Ui::Text::SingleCustomEmoji(SerializeCustomEmojiId(id)); +} + +TextWithEntities SingleCustomEmoji(not_null document) { + return SingleCustomEmoji(document->id); } bool AllowEmojiWithoutPremium(not_null peer) { diff --git a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h index 53df8fb25..07c651c3e 100644 --- a/Telegram/SourceFiles/data/stickers/data_custom_emoji.h +++ b/Telegram/SourceFiles/data/stickers/data_custom_emoji.h @@ -21,10 +21,6 @@ namespace Data { class Session; class CustomEmojiLoader; -struct CustomEmojiId { - DocumentId id = 0; -}; - enum class CustomEmojiSizeTag : uchar { Normal, Large, @@ -176,10 +172,14 @@ private: [[nodiscard]] int FrameSizeFromTag(CustomEmojiManager::SizeTag tag); -[[nodiscard]] QString SerializeCustomEmojiId(const CustomEmojiId &id); +[[nodiscard]] QString SerializeCustomEmojiId(DocumentId id); [[nodiscard]] QString SerializeCustomEmojiId( not_null document); -[[nodiscard]] CustomEmojiId ParseCustomEmojiData(QStringView data); +[[nodiscard]] DocumentId ParseCustomEmojiData(QStringView data); + +[[nodiscard]] TextWithEntities SingleCustomEmoji(DocumentId id); +[[nodiscard]] TextWithEntities SingleCustomEmoji( + not_null document); [[nodiscard]] bool AllowEmojiWithoutPremium(not_null peer); diff --git a/Telegram/SourceFiles/dialogs/dialogs.style b/Telegram/SourceFiles/dialogs/dialogs.style index 043a09199..51f2b6f81 100644 --- a/Telegram/SourceFiles/dialogs/dialogs.style +++ b/Telegram/SourceFiles/dialogs/dialogs.style @@ -69,7 +69,7 @@ defaultDialogRow: DialogRow { nameLeft: 68px; nameTop: 10px; textLeft: 68px; - textTop: 35px; + textTop: 34px; } dialogsOnlineBadgeStroke: 2px; @@ -460,3 +460,7 @@ chooseTopicListItem: PeerListItem(defaultPeerListItem) { chooseTopicList: PeerList(defaultPeerList) { item: chooseTopicListItem; } + +dialogsTopicArrow: icon{{ "dialogs/dialogs_topic_arrow", dialogsTextFgService }}; +dialogsTopicArrowSkip: 13px; +dialogsTopicArrowTop: 4px; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 574d6bde2..da7a52eaa 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -921,7 +921,7 @@ void RowPainter::Paint( view->prepare( item, [=] { entry->updateChatListEntry(); }, - {}); + { .ignoreTopic = (!history || !peer->isForum()) }); } view->paint(p, rect, context); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp index d4fbfe896..0a7a7446a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.cpp @@ -18,19 +18,22 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/painter.h" #include "core/ui_integration.h" #include "lang/lang_keys.h" +#include "lang/lang_text_entity.h" #include "styles/style_dialogs.h" namespace { template 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) { - return { text }; + return { { text } }; } - QString text; + TextWithEntities text; int offset = -1; }; @@ -52,12 +55,12 @@ TextWithTagOffset ReplaceTag>::Call( ushort tag, const TextWithTagOffset &replacement) { const auto replacementPosition = FindTagReplacementPosition( - original.text, + original.text.text, tag); if (replacementPosition < 0) { return std::move(original); } - original.text = ReplaceTag::Replace( + original.text = ReplaceTag::Replace( std::move(original.text), replacement.text, replacementPosition); @@ -65,7 +68,8 @@ TextWithTagOffset ReplaceTag>::Call( original.offset = replacementPosition; } else if (original.offset > replacementPosition) { 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); } @@ -105,6 +109,7 @@ struct MessageView::LoadingContext { MessageView::MessageView() : _senderCache(st::dialogsTextWidthMin) +, _topicCache(st::dialogsTextWidthMin) , _textCache(st::dialogsTextWidthMin) { } @@ -130,28 +135,46 @@ void MessageView::prepare( ToPreviewOptions options) { options.existing = &_imagesCache; auto preview = item->toPreview(options); - if (!preview.images.empty() && preview.imagesInTextPosition > 0) { - auto sender = ::Ui::Text::Mid( - preview.text, - 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 hasImages = !preview.images.empty(); + const auto hasArrow = (preview.arrowInTextPosition > 0) + && (preview.imagesInTextPosition > preview.arrowInTextPosition); const auto history = item->history(); const auto context = Core::MarkedTextContext{ .session = &history->session(), .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( st::dialogsTextStyle, DialogsPreviewText(std::move(preview.text)), @@ -193,16 +216,46 @@ void MessageView::paint( : st::dialogsTextPalette); auto rect = geometry; + const auto lines = rect.height() / st::dialogsTextFont->height; if (!_senderCache.isEmpty()) { _senderCache.draw(p, { .position = rect.topLeft(), .availableWidth = rect.width(), .palette = palette, - .elisionLines = rect.height() / st::dialogsTextFont->height, + .elisionLines = lines, }); - const auto skip = st::dialogsMiniPreviewSkip - + st::dialogsMiniPreviewRight; - rect.setLeft(rect.x() + _senderCache.maxWidth() + skip); + rect.setLeft(rect.x() + _senderCache.maxWidth()); + if (!_topicCache.isEmpty() || _imagesCache.empty()) { + 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) { if (rect.width() < st::dialogsMiniPreview) { @@ -229,30 +282,48 @@ void MessageView::paint( .spoiler = Text::DefaultSpoilerCache(), .now = context.now, .paused = context.paused, - .elisionLines = rect.height() / st::dialogsTextFont->height, + .elisionLines = lines, }); } HistoryView::ItemPreview PreviewWithSender( HistoryView::ItemPreview &&preview, - const TextWithEntities &sender) { - const auto textWithOffset = tr::lng_dialogs_text_with_from( + const QString &sender, + TextWithEntities topic) { + auto senderWithOffset = topic.empty() + ? TextWithTagOffset::FromString(sender) + : tr::lng_dialogs_text_from_in_topic( + tr::now, + lt_from, + { sender }, + lt_topic, + std::move(topic), + TextWithTagOffset::FromString); + auto wrappedWithOffset = tr::lng_dialogs_text_from_wrapped( + tr::now, + lt_from, + std::move(senderWithOffset.text), + TextWithTagOffset::FromString); + const auto wrappedSize = wrappedWithOffset.text.text.size(); + auto fullWithOffset = tr::lng_dialogs_text_with_from( tr::now, lt_from_part, - sender.text, - lt_message, - preview.text.text, - TextWithTagOffset::FromString); - preview.text = tr::lng_dialogs_text_with_from( - tr::now, - lt_from_part, - sender, + Ui::Text::PlainLink(std::move(wrappedWithOffset.text)), lt_message, std::move(preview.text), - Ui::Text::WithEntities); - preview.imagesInTextPosition = (textWithOffset.offset < 0) + TextWithTagOffset::FromString); + 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 - : textWithOffset.offset + sender.text.size(); + : (fullWithOffset.offset + wrappedSize); return std::move(preview); } diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h index 4423b9c5c..9b3ed4758 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_message_view.h @@ -57,6 +57,7 @@ private: mutable const HistoryItem *_textCachedFor = nullptr; mutable Text::String _senderCache; + mutable Text::String _topicCache; mutable Text::String _textCache; mutable std::vector _imagesCache; mutable std::unique_ptr _loadingContext; @@ -65,6 +66,7 @@ private: [[nodiscard]] HistoryView::ItemPreview PreviewWithSender( HistoryView::ItemPreview &&preview, - const TextWithEntities &sender); + const QString &sender, + TextWithEntities topic); } // namespace Dialogs::Ui diff --git a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp index 78f1cd920..e9e4441b5 100644 --- a/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/admin_log/history_admin_log_item.cpp @@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_text_entities.h" #include "data/data_channel.h" #include "data/data_file_origin.h" +#include "data/data_forum_topic.h" #include "data/data_user.h" #include "data/data_session.h" #include "data/data_message_reaction_id.h" @@ -624,23 +625,10 @@ TextWithEntities GenerateDefaultBannedRightsChangeText( not_null channel, const MTPForumTopic &topic) { 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( - 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( peerToChannel(channel->id).bare).arg( data.vid().v)); diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index f10e85ffd..be28ba68a 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1365,9 +1365,11 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const { if (!sender) { return result; } - const auto fromWrapped = Ui::Text::PlainLink( - tr::lng_dialogs_text_from_wrapped(tr::now, lt_from, *sender)); - return Dialogs::Ui::PreviewWithSender(std::move(result), fromWrapped); + const auto topic = options.ignoreTopic ? nullptr : this->topic(); + return Dialogs::Ui::PreviewWithSender( + std::move(result), + *sender, + topic ? topic->titleWithIcon() : TextWithEntities()); } TextWithEntities HistoryItem::inReplyText() const { diff --git a/Telegram/SourceFiles/history/history_service.cpp b/Telegram/SourceFiles/history/history_service.cpp index 6e85e8a72..37b2024bc 100644 --- a/Telegram/SourceFiles/history/history_service.cpp +++ b/Telegram/SourceFiles/history/history_service.cpp @@ -636,33 +636,19 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { return result; }; - const auto wrapTopicIcon = [](DocumentId id) { - return TextWithEntities{ - "@", - { EntityInText( - EntityType::CustomEmoji, - 0, - 1, - Data::SerializeCustomEmojiId({ .id = id })) - }, - }; - }; - auto prepareTopicCreate = [&](const MTPDmessageActionTopicCreate &action) { 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 .arg(peerToChannel(history()->peer->id).bare) .arg(id.bare); result.text = tr::lng_action_topic_created( tr::now, 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); return result; }; @@ -684,7 +670,7 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { lt_link, { tr::lng_action_topic_placeholder(tr::now) }, lt_emoji, - wrapTopicIcon(iconId), + Data::SingleCustomEmoji(iconId), Ui::Text::WithEntities); } else { result.links.push_back(fromLink()); @@ -699,14 +685,6 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { } } else { 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( tr::now, lt_from, @@ -714,7 +692,9 @@ void HistoryService::setMessageByAction(const MTPmessageAction &action) { lt_link, { tr::lng_action_topic_placeholder(tr::now) }, lt_title, - std::move(title), + Data::ForumTopicIconWithTitle( + action.vicon_emoji_id().value_or_empty(), + qs(*action.vtitle())), Ui::Text::WithEntities); } if (result.text.empty()) { diff --git a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp index 3771bfa47..7ed6de1f2 100644 --- a/Telegram/SourceFiles/history/view/history_view_context_menu.cpp +++ b/Telegram/SourceFiles/history/view/history_view_context_menu.cpp @@ -1311,7 +1311,7 @@ std::vector CollectEmojiPacks( for (const auto &entity : item->originalText().entities) { if (entity.type() == EntityType::CustomEmoji) { const auto data = Data::ParseCustomEmojiData(entity.data()); - push(data.id); + push(data); } } break; diff --git a/Telegram/SourceFiles/history/view/history_view_element.cpp b/Telegram/SourceFiles/history/view/history_view_element.cpp index d9d03600e..1b3931950 100644 --- a/Telegram/SourceFiles/history/view/history_view_element.cpp +++ b/Telegram/SourceFiles/history/view/history_view_element.cpp @@ -690,17 +690,6 @@ auto Element::contextDependentServiceText() -> TextWithLinks { return {}; } 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 .arg(peerToChannel(peerId).bare) .arg(topicRootId.bare); @@ -715,11 +704,9 @@ auto Element::contextDependentServiceText() -> TextWithLinks { const auto wrapTopic = [&]( const QString &title, std::optional iconId) { - auto result = TextWithEntities{ title }; - auto full = (iconId && *iconId) - ? wrapIcon(*iconId).append(' ').append(std::move(result)) - : result; - return Ui::Text::Link(std::move(full), topicUrl); + return Ui::Text::Link( + Data::ForumTopicIconWithTitle(iconId.value_or(0), title), + topicUrl); }; const auto wrapParentTopic = [&] { const auto forum = history()->asForum(); @@ -783,7 +770,7 @@ auto Element::contextDependentServiceText() -> TextWithLinks { lt_link, placeholderLink(), lt_emoji, - wrapIcon(iconId), + Data::SingleCustomEmoji(iconId), Ui::Text::WithEntities), { from->createOpenLink() }, }; diff --git a/Telegram/SourceFiles/history/view/history_view_item_preview.h b/Telegram/SourceFiles/history/view/history_view_item_preview.h index 20e4a2f6e..6bf76ecf9 100644 --- a/Telegram/SourceFiles/history/view/history_view_item_preview.h +++ b/Telegram/SourceFiles/history/view/history_view_item_preview.h @@ -21,6 +21,7 @@ struct ItemPreviewImage { struct ItemPreview { TextWithEntities text; std::vector images; + int arrowInTextPosition = -1; int imagesInTextPosition = 0; std::any loadingContext; }; @@ -31,7 +32,7 @@ struct ToPreviewOptions { bool hideCaption = false; bool generateImages = true; bool ignoreGroup = false; - bool ignoreSpoilers = false; + bool ignoreTopic = true; }; } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/media/history_view_custom_emoji.cpp b/Telegram/SourceFiles/history/view/media/history_view_custom_emoji.cpp index 9cc707b70..63fa634f9 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_custom_emoji.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_custom_emoji.cpp @@ -92,7 +92,7 @@ CustomEmoji::CustomEmoji( tag)); } else { const auto &data = element.entityData; - const auto id = Data::ParseCustomEmojiData(data).id; + const auto id = Data::ParseCustomEmojiData(data); const auto document = owner->document(id); if (document->sticker()) { _lines.back().push_back(createStickerPart(document)); diff --git a/Telegram/SourceFiles/lang/lang_text_entity.cpp b/Telegram/SourceFiles/lang/lang_text_entity.cpp index 47e7b886e..5bdc1bdc6 100644 --- a/Telegram/SourceFiles/lang/lang_text_entity.cpp +++ b/Telegram/SourceFiles/lang/lang_text_entity.cpp @@ -16,25 +16,28 @@ TextWithEntities ReplaceTag::Call(TextWithEntities &&original, if (replacementPosition < 0) { return std::move(original); } + return Replace(std::move(original), replacement, replacementPosition); +} +TextWithEntities ReplaceTag::Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start) { auto result = TextWithEntities(); - result.text = ReplaceTag::Replace(std::move(original.text), replacement.text, replacementPosition); + result.text = ReplaceTag::Replace(std::move(original.text), replacement.text, start); auto originalEntitiesCount = original.entities.size(); auto replacementEntitiesCount = replacement.entities.size(); if (originalEntitiesCount != 0 || replacementEntitiesCount != 0) { 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 addReplacementEntitiesUntil = [&replacementEntity, &replacement, &result, replacementPosition, replacementEnd](int untilPosition) { + auto addReplacementEntitiesUntil = [&](int untilPosition) { while (replacementEntity != replacement.entities.cend()) { - auto newOffset = replacementPosition + replacementEntity->offset(); + auto newOffset = start + replacementEntity->offset(); if (newOffset >= untilPosition) { return; } auto newEnd = newOffset + replacementEntity->length(); - newOffset = std::clamp(newOffset, replacementPosition, replacementEnd); - newEnd = std::clamp(newEnd, replacementPosition, replacementEnd); + newOffset = std::clamp(newOffset, start, replacementEnd); + newEnd = std::clamp(newEnd, start, replacementEnd); if (auto newLength = newEnd - newOffset) { result.entities.push_back({ replacementEntity->type(), newOffset, newLength, replacementEntity->data() }); } @@ -46,10 +49,10 @@ TextWithEntities ReplaceTag::Call(TextWithEntities &&original, // Transform the entity by the replacement. auto offset = entity.offset(); auto end = offset + entity.length(); - if (offset > replacementPosition) { + if (offset > start) { offset = offset + replacement.text.size() - kTagReplacementSize; } - if (end > replacementPosition) { + if (end > start) { end = end + replacement.text.size() - kTagReplacementSize; } offset = std::clamp(offset, 0, int(result.text.size())); diff --git a/Telegram/SourceFiles/lang/lang_text_entity.h b/Telegram/SourceFiles/lang/lang_text_entity.h index 1cc22586d..b1204ba1e 100644 --- a/Telegram/SourceFiles/lang/lang_text_entity.h +++ b/Telegram/SourceFiles/lang/lang_text_entity.h @@ -27,6 +27,7 @@ struct ReplaceTag; template <> struct ReplaceTag { static TextWithEntities Call(TextWithEntities &&original, ushort tag, const TextWithEntities &replacement); + static TextWithEntities Replace(TextWithEntities &&original, const TextWithEntities &replacement, int start); }; diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index f3951b34e..c349686ba 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -848,7 +848,7 @@ TextWithEntities Manager::ComposeReactionEmoji( EntityType::CustomEmoji, 0, text.size(), - Data::SerializeCustomEmojiId(Data::CustomEmojiId{ id })) + Data::SerializeCustomEmojiId(id)) } }; } diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 533bda271..e36bb9aed 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -968,24 +968,9 @@ void Notification::updateNotifyDisplay() { const auto topicWithChat = [&]() -> TextWithEntities { const auto name = _history->peer->name(); - const auto wrapIcon = [](DocumentId id) { - return TextWithEntities{ - "@", - { 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 + ')'); + return _topic + ? _topic->titleWithIcon().append(u" ("_q + name + ')') + : TextWithEntities{ name }; }; auto title = options.hideNameAndPhoto ? TextWithEntities{ u"Telegram Desktop"_q } diff --git a/Telegram/lib_ui b/Telegram/lib_ui index c3616927e..4539d0bab 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit c3616927ebfcbe98375189be87821bea202d9587 +Subproject commit 4539d0bab4ffc335de79b77ee9632f6a7b59d48b