From 9ace04d2c9fd55f3f9e327732ea75e34bfbfc885 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 26 Sep 2024 22:05:58 +0400 Subject: [PATCH] Support entities in star gift messages. --- .../SourceFiles/boxes/gift_credits_box.cpp | 63 +++++++++++++++---- Telegram/SourceFiles/boxes/send_files_box.cpp | 17 ++--- Telegram/SourceFiles/boxes/share_box.cpp | 13 ++-- .../chat_helpers/message_field.cpp | 56 +++++++++-------- .../SourceFiles/chat_helpers/message_field.h | 19 +++--- .../view/media/history_view_media_generic.cpp | 13 +++- .../view/media/history_view_media_generic.h | 3 +- .../view/media/history_view_service_box.cpp | 63 +++++++++++++++++-- .../view/media/history_view_service_box.h | 1 + .../window/notifications_manager_default.cpp | 9 ++- .../SourceFiles/window/window_peer_menu.cpp | 13 ++-- 11 files changed, 191 insertions(+), 79 deletions(-) diff --git a/Telegram/SourceFiles/boxes/gift_credits_box.cpp b/Telegram/SourceFiles/boxes/gift_credits_box.cpp index c0db5d248..2b8694235 100644 --- a/Telegram/SourceFiles/boxes/gift_credits_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_credits_box.cpp @@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "boxes/peer_list_controllers.h" #include "boxes/send_credits_box.h" +#include "chat_helpers/emoji_suggestions_widget.h" +#include "chat_helpers/message_field.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "chat_helpers/stickers_lottie.h" #include "core/ui_integration.h" @@ -65,7 +67,7 @@ namespace { constexpr auto kPriceTabAll = 0; constexpr auto kPriceTabLimited = -1; -constexpr auto kGiftMessageLimit = 256; +constexpr auto kGiftMessageLimit = 255; constexpr auto kSentToastDuration = 3 * crl::time(1000); using namespace HistoryView; @@ -83,7 +85,7 @@ struct GiftsDescriptor { struct GiftDetails { GiftDescriptor descriptor; - QString text; + TextWithEntities text; bool anonymous = false; }; @@ -160,14 +162,16 @@ auto GenerateGiftMedia( auto pushText = [&]( TextWithEntities text, QMargins margins = {}, - const base::flat_map &links = {}) { + const base::flat_map &links = {}, + const std::any &context = {}) { if (text.empty()) { return; } push(std::make_unique( std::move(text), margins, - links)); + links, + context)); }; const auto sticker = [=] { using Tag = ChatHelpers::StickerLottieSize; @@ -202,11 +206,18 @@ auto GenerateGiftMedia( lt_count, v::get(descriptor).convertStars, Ui::Text::RichLangValue); - auto description = data.text.isEmpty() + auto description = data.text.empty() ? std::move(textFallback) - : TextWithEntities{ data.text }; + : data.text; pushText(Ui::Text::Bold(title), st::giftBoxPreviewTitlePadding); - pushText(std::move(description), st::giftBoxPreviewTextPadding); + pushText( + std::move(description), + st::giftBoxPreviewTextPadding, + {}, + Core::MarkedTextContext{ + .session = &parent->data()->history()->session(), + .customEmojiRepaint = [parent] { parent->repaint(); }, + }); }; } @@ -732,10 +743,6 @@ void SendGiftBox( }); const auto session = &window->session(); - const auto context = Core::MarkedTextContext{ - .session = session, - .customEmojiRepaint = [] {}, - }; auto cost = rpl::single([&] { return v::match(descriptor, [&](const GiftTypePremium &data) { if (data.currency == Ui::kCreditsCurrency) { @@ -777,10 +784,40 @@ void SendGiftBox( kGiftMessageLimit); text->changes() | rpl::start_with_next([=] { auto now = state->details.current(); - now.text = text->getLastText(); + auto textWithTags = text->getTextWithAppliedMarkdown(); + now.text = TextWithEntities{ + std::move(textWithTags.text), + TextUtilities::ConvertTextTagsToEntities(textWithTags.tags) + }; state->details = std::move(now); }, text->lifetime()); + const auto allow = [=](not_null emoji) { + return true; + }; + InitMessageFieldHandlers({ + .session = &window->session(), + .show = window->uiShow(), + .field = text, + .customEmojiPaused = [=] { + using namespace Window; + return window->isGifPausedAtLeastFor(GifPauseReason::Layer); + }, + .allowPremiumEmoji = allow, + .allowMarkdownTags = { + Ui::InputField::kTagBold, + Ui::InputField::kTagItalic, + Ui::InputField::kTagUnderline, + Ui::InputField::kTagStrikeOut, + Ui::InputField::kTagSpoiler, + } + }); + Ui::Emoji::SuggestionsController::Init( + box->getDelegate()->outerContainer(), + text, + &window->session(), + { .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow }); + AddDivider(container); AddSkip(container); container->add( @@ -825,7 +862,7 @@ void SendGiftBox( Payments::CheckoutProcess::Start(Payments::InvoiceStarGift{ .giftId = gift.id, .randomId = state->randomId, - .message = { details.text }, + .message = details.text, .user = peer->asUser(), .anonymous = details.anonymous, }, done, Payments::ProcessNonPanelPaymentFormFactory(window, done)); diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index f4d286544..17cfeefb8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -1267,13 +1267,16 @@ void SendFilesBox::setupCaption() { : (_limits & SendFilesAllow::EmojiWithoutPremium); }; const auto show = _show; - InitMessageFieldHandlers( - &show->session(), - show, - _caption.data(), - [=] { return show->paused(Window::GifPauseReason::Layer); }, - allow, - &_st.files.caption); + InitMessageFieldHandlers({ + .session = &show->session(), + .show = show, + .field = _caption.data(), + .customEmojiPaused = [=] { + return show->paused(Window::GifPauseReason::Layer); + }, + .allowPremiumEmoji = allow, + .fieldStyle = &_st.files.caption, + }); setupCaptionAutocomplete(); Ui::Emoji::SuggestionsController::Init( getDelegate()->outerContainer(), diff --git a/Telegram/SourceFiles/boxes/share_box.cpp b/Telegram/SourceFiles/boxes/share_box.cpp index d2891c984..bd154f8c4 100644 --- a/Telegram/SourceFiles/boxes/share_box.cpp +++ b/Telegram/SourceFiles/boxes/share_box.cpp @@ -240,13 +240,12 @@ void ShareBox::prepareCommentField() { }, field->lifetime()); if (const auto show = uiShow(); show->valid()) { - InitMessageFieldHandlers( - _descriptor.session, - Main::MakeSessionShow(show, _descriptor.session), - field, - nullptr, - nullptr, - _descriptor.stLabel); + InitMessageFieldHandlers({ + .session = _descriptor.session, + .show = Main::MakeSessionShow(show, _descriptor.session), + .field = field, + .fieldStyle = _descriptor.stLabel, + }); } field->setSubmitSettings(Core::App().settings().sendSubmitWay()); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 713687f18..9cc3a925a 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -423,18 +423,14 @@ Fn save)> DefaultEditLanguageCallback( }; } -void InitMessageFieldHandlers( - not_null session, - std::shared_ptr show, - not_null field, - Fn customEmojiPaused, - Fn)> allowPremiumEmoji, - const style::InputField *fieldStyle) { - const auto paused = [customEmojiPaused] { - return customEmojiPaused && customEmojiPaused(); +void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args) { + const auto paused = [passed = args.customEmojiPaused] { + return passed && passed(); }; + const auto field = args.field; + const auto session = args.session; field->setTagMimeProcessor( - FieldTagMimeProcessor(session, allowPremiumEmoji)); + FieldTagMimeProcessor(session, args.allowPremiumEmoji)); field->setCustomTextContext([=](Fn repaint) { return std::any(Core::MarkedTextContext{ .session = session, @@ -448,12 +444,14 @@ void InitMessageFieldHandlers( field->setInstantReplaces(Ui::InstantReplaces::Default()); field->setInstantReplacesEnabled( Core::App().settings().replaceEmojiValue()); - field->setMarkdownReplacesEnabled(true); - if (show) { + field->setMarkdownReplacesEnabled(rpl::single(Ui::MarkdownEnabledState{ + Ui::MarkdownEnabled{ std::move(args.allowMarkdownTags) } + })); + if (const auto &show = args.show) { field->setEditLinkCallback( - DefaultEditLinkCallback(show, field, fieldStyle)); + DefaultEditLinkCallback(show, field, args.fieldStyle)); field->setEditLanguageCallback(DefaultEditLanguageCallback(show)); - InitSpellchecker(show, field, fieldStyle != nullptr); + InitSpellchecker(show, field, args.fieldStyle != nullptr); } const auto style = field->lifetime().make_state( session->colorIndicesValue()); @@ -553,12 +551,15 @@ void InitMessageFieldHandlers( not_null field, ChatHelpers::PauseReason pauseReasonLevel, Fn)> allowPremiumEmoji) { - InitMessageFieldHandlers( - &controller->session(), - controller->uiShow(), - field, - [=] { return controller->isGifPausedAtLeastFor(pauseReasonLevel); }, - allowPremiumEmoji); + InitMessageFieldHandlers({ + .session = &controller->session(), + .show = controller->uiShow(), + .field = field, + .customEmojiPaused = [=] { + return controller->isGifPausedAtLeastFor(pauseReasonLevel); + }, + .allowPremiumEmoji = std::move(allowPremiumEmoji), + }); } void InitMessageFieldGeometry(not_null field) { @@ -574,12 +575,15 @@ void InitMessageField( std::shared_ptr show, not_null field, Fn)> allowPremiumEmoji) { - InitMessageFieldHandlers( - &show->session(), - show, - field, - [=] { return show->paused(ChatHelpers::PauseReason::Any); }, - std::move(allowPremiumEmoji)); + InitMessageFieldHandlers({ + .session = &show->session(), + .show = show, + .field = field, + .customEmojiPaused = [=] { + return show->paused(ChatHelpers::PauseReason::Any); + }, + .allowPremiumEmoji = std::move(allowPremiumEmoji), + }); InitMessageFieldGeometry(field); } diff --git a/Telegram/SourceFiles/chat_helpers/message_field.h b/Telegram/SourceFiles/chat_helpers/message_field.h index 041b54d66..0557a8951 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.h +++ b/Telegram/SourceFiles/chat_helpers/message_field.h @@ -54,13 +54,18 @@ Fn save)> DefaultEditLanguageCallback( std::shared_ptr show); -void InitMessageFieldHandlers( - not_null session, - std::shared_ptr show, // may be null - not_null field, - Fn customEmojiPaused, - Fn)> allowPremiumEmoji = nullptr, - const style::InputField *fieldStyle = nullptr); + +struct MessageFieldHandlersArgs { + not_null session; + std::shared_ptr show; // may be null + not_null field; + Fn customEmojiPaused; + Fn)> allowPremiumEmoji; + const style::InputField *fieldStyle = nullptr; + base::flat_set allowMarkdownTags; +}; +void InitMessageFieldHandlers(MessageFieldHandlersArgs &&args); + void InitMessageFieldHandlers( not_null controller, not_null field, diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp index 6f970edf5..18625b684 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.cpp @@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/dynamic_image.h" #include "ui/dynamic_thumbnails.h" #include "ui/painter.h" +#include "ui/power_saving.h" #include "ui/rect.h" #include "ui/round_rect.h" #include "styles/style_chat.h" @@ -222,10 +223,15 @@ QMargins MediaGeneric::inBubblePadding() const { MediaGenericTextPart::MediaGenericTextPart( TextWithEntities text, QMargins margins, - const base::flat_map &links) + const base::flat_map &links, + const std::any &context) : _text(st::msgMinWidth) , _margins(margins) { - _text.setMarkedText(st::defaultTextStyle, text); + _text.setMarkedText( + st::defaultTextStyle, + text, + kMarkupTextOptions, + context); for (const auto &[index, link] : links) { _text.setLink(index, link); } @@ -248,7 +254,10 @@ void MediaGenericTextPart::draw( .palette = &(service ? context.st->serviceTextPalette() : context.messageStyle()->textPalette), + .spoiler = Ui::Text::DefaultSpoilerCache(), .now = context.now, + .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), + .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), }); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h index bc7f18737..f406e4449 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_media_generic.h +++ b/Telegram/SourceFiles/history/view/media/history_view_media_generic.h @@ -121,7 +121,8 @@ public: MediaGenericTextPart( TextWithEntities text, QMargins margins, - const base::flat_map &links = {}); + const base::flat_map &links = {}, + const std::any &context = {}); void draw( Painter &p, diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp index 08bd9408a..0252e3281 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.cpp @@ -6,14 +6,20 @@ For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "history/view/media/history_view_service_box.h" -// -#include "history/view/history_view_cursor_state.h" + +#include "core/ui_integration.h" #include "history/view/media/history_view_sticker_player_abstract.h" +#include "history/view/history_view_cursor_state.h" +#include "history/view/history_view_element.h" +#include "history/view/history_view_text_helper.h" +#include "history/history.h" #include "lang/lang_keys.h" #include "ui/chat/chat_style.h" +#include "ui/effects/animation_value.h" #include "ui/effects/ripple_animation.h" #include "ui/text/text_utilities.h" #include "ui/painter.h" +#include "ui/power_saving.h" #include "styles/style_chat.h" #include "styles/style_premium.h" #include "styles/style_layers.h" @@ -48,9 +54,15 @@ ServiceBox::ServiceBox( EntityType::StrikeOut, EntityType::Underline, EntityType::Italic, + EntityType::Spoiler, + EntityType::CustomEmoji, }), kMarkupTextOptions, - _maxWidth) + _maxWidth, + Core::MarkedTextContext{ + .session = &parent->history()->session(), + .customEmojiRepaint = [parent] { parent->customEmojiRepaint(); }, + }) , _size( _content->width(), (st::msgServiceGiftBoxTopSkip @@ -67,6 +79,7 @@ ServiceBox::ServiceBox( : (_content->buttonSkip() + st::msgServiceGiftBoxButtonHeight)) + st::msgServiceGiftBoxButtonMargins.bottom())) , _innerSize(_size - QSize(0, st::msgServiceGiftBoxTopSkip)) { + InitElementTextPart(_parent, _subtitle); if (auto text = _content->button()) { _button.repaint = [=] { repaint(); }; std::move(text) | rpl::start_with_next([=](QString value) { @@ -116,7 +129,17 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const { _title.draw(p, st::msgPadding.left(), top, _maxWidth, style::al_top); top += _title.countHeight(_maxWidth) + padding.bottom(); } - _subtitle.draw(p, st::msgPadding.left(), top, _maxWidth, style::al_top); + _parent->prepareCustomEmojiPaint(p, context, _subtitle); + _subtitle.draw(p, { + .position = QPoint(st::msgPadding.left(), top), + .availableWidth = _maxWidth, + .align = style::al_top, + .palette = &context.st->serviceTextPalette(), + .spoiler = Ui::Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), + .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), + }); top += _subtitle.countHeight(_maxWidth) + padding.bottom(); } @@ -180,8 +203,30 @@ void ServiceBox::draw(Painter &p, const PaintContext &context) const { TextState ServiceBox::textState(QPoint point, StateRequest request) const { auto result = TextState(_parent); + const auto content = contentRect(); + const auto lookupSubtitleLink = [&] { + auto top = st::msgServiceGiftBoxTopSkip + + content.top() + + content.height(); + const auto &padding = st::msgServiceGiftBoxTitlePadding; + top += padding.top(); + if (!_title.isEmpty()) { + top += _title.countHeight(_maxWidth) + padding.bottom(); + } + auto subtitleRequest = request.forText(); + subtitleRequest.align = style::al_top; + const auto state = _subtitle.getState( + point - QPoint(st::msgPadding.left(), top), + _maxWidth, + subtitleRequest); + if (state.link) { + result.link = state.link; + } + }; if (_button.empty()) { - if (QRect(QPoint(), _innerSize).contains(point)) { + if (!_button.link) { + lookupSubtitleLink(); + } else if (QRect(QPoint(), _innerSize).contains(point)) { result.link = _button.link; } } else { @@ -189,11 +234,13 @@ TextState ServiceBox::textState(QPoint point, StateRequest request) const { if (rect.contains(point)) { result.link = _button.link; _button.lastPoint = point - rect.topLeft(); - } else if (contentRect().contains(point)) { + } else if (content.contains(point)) { if (!_contentLink) { _contentLink = _content->createViewLink(); } result.link = _contentLink; + } else { + lookupSubtitleLink(); } } return result; @@ -238,6 +285,10 @@ bool ServiceBox::customInfoLayout() const { return false; } +void ServiceBox::hideSpoilers() { + _subtitle.setSpoilerRevealed(false, anim::type::instant); +} + bool ServiceBox::hasHeavyPart() const { return _content->hasHeavyPart(); } diff --git a/Telegram/SourceFiles/history/view/media/history_view_service_box.h b/Telegram/SourceFiles/history/view/media/history_view_service_box.h index 09ee92561..2a52e7336 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_service_box.h +++ b/Telegram/SourceFiles/history/view/media/history_view_service_box.h @@ -81,6 +81,7 @@ public: [[nodiscard]] bool hideServiceText() const override { return _content->hideServiceText(); } + void hideSpoilers() override; bool hasHeavyPart() const override; void unloadHeavyPart() override; diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 05b46f74c..198418b58 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -1091,11 +1091,10 @@ void Notification::showReplyField() { _replyArea->setFocus(); _replyArea->setMaxLength(MaxMessageSize); _replyArea->setSubmitSettings(Ui::InputField::SubmitSettings::Both); - InitMessageFieldHandlers( - &_item->history()->session(), - nullptr, - _replyArea.data(), - nullptr); + InitMessageFieldHandlers({ + .session = &_item->history()->session(), + .field = _replyArea.data(), + }); // Catch mouse press event to activate the window. QCoreApplication::instance()->installEventFilter(this); diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 2bc8dbd29..912f5f64a 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -2215,11 +2215,14 @@ QPointer ShowForwardMessagesBox( field->submits( ) | rpl::start_with_next([=] { submit({}); }, field->lifetime()); - InitMessageFieldHandlers( - session, - show, - field, - [=] { return show->paused(GifPauseReason::Layer); }); + InitMessageFieldHandlers({ + .session = session, + .show = show, + .field = field, + .customEmojiPaused = [=] { + return show->paused(GifPauseReason::Layer); + }, + }); field->setSubmitSettings(Core::App().settings().sendSubmitWay()); Ui::SendPendingMoveResizeEvents(comment);