diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index abe8686890..3c5d35d2d8 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -35,6 +35,7 @@ struct SendOptions { bool handleSupportSwitch = false; bool invertCaption = false; bool hideViaBot = false; + bool mediaSpoiler = false; crl::time ttlSeconds = 0; SuggestOptions suggest; diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index 8830f771f2..af16225bd5 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -247,6 +247,7 @@ void SendExistingMedia( .postAuthor = NewMessagePostAuthor(action), .effectId = action.options.effectId, .suggest = HistoryMessageSuggestInfo(action.options), + .mediaSpoiler = action.options.mediaSpoiler, }, media, caption); const auto performRequest = [=](const auto &repeatRequest) -> void { @@ -302,7 +303,9 @@ void SendExistingDocument( std::optional localMessageId) { const auto inputMedia = [=] { return MTP_inputMediaDocument( - MTP_flags(0), + MTP_flags(message.action.options.mediaSpoiler + ? MTPDinputMediaDocument::Flag::f_spoiler + : MTPDinputMediaDocument::Flags(0)), document->mtpInput(), MTPInputPhoto(), // video_cover MTPint(), // ttl_seconds diff --git a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp index e7f779c05e..7ee11ad7dc 100644 --- a/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp +++ b/Telegram/SourceFiles/boxes/send_gif_with_caption_box.cpp @@ -36,31 +36,39 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "menu/menu_send.h" #include "ui/controls/emoji_button.h" #include "ui/controls/emoji_button_factory.h" +#include "ui/effects/spoiler_mess.h" +#include "ui/image/image_prepare.h" #include "ui/layers/generic_box.h" #include "ui/rect.h" #include "ui/text/text_entity.h" #include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "ui/widgets/fields/input_field.h" +#include "ui/widgets/popup_menu.h" #include "window/window_controller.h" #include "window/window_session_controller.h" #include "styles/style_boxes.h" #include "styles/style_chat_helpers.h" #include "styles/style_layers.h" +#include "styles/style_menu_icons.h" namespace Ui { namespace { -[[nodiscard]] not_null AddGifWidget( +struct State final { + std::shared_ptr mediaView; + ::Media::Clip::ReaderPointer gif; + std::unique_ptr spoiler; + QImage firstFrame; + QImage blurredFrame; + bool hasSpoiler = false; + rpl::lifetime loadingLifetime; +}; + +[[nodiscard]] not_null AddGifWidget( not_null container, not_null document, int width) { - struct State final { - std::shared_ptr mediaView; - ::Media::Clip::ReaderPointer gif; - rpl::lifetime loadingLifetime; - }; - const auto state = container->lifetime().make_state(); state->mediaView = document->createMediaView(); state->mediaView->automaticLoad(Data::FileOriginSavedGifs(), nullptr); @@ -75,9 +83,40 @@ namespace { std::numeric_limits::max(), Qt::KeepAspectRatio).height()), st::boxRowPadding); - widget->paintRequest( - ) | rpl::on_next([=] { - auto p = QPainter(widget); + widget->paintOn([=](QPainter &p) { + if (state->hasSpoiler) { + if (state->firstFrame.isNull() + && state->gif + && state->gif->ready()) { + state->firstFrame = state->gif->current( + { .frame = widget->size() }, + crl::now()); + state->blurredFrame = Images::BlurLargeImage( + base::duplicate(state->firstFrame), + 24); + } + if (!state->blurredFrame.isNull()) { + p.drawImage(0, 0, state->blurredFrame); + } else if (const auto thumb = state->mediaView->thumbnail()) { + p.drawImage( + widget->rect(), + thumb->pixNoCache( + widget->size() * style::DevicePixelRatio(), + { + .options = Images::Option::Blur, + .outer = widget->size(), + }).toImage()); + } + if (!state->spoiler) { + state->spoiler = std::make_unique( + [=] { widget->update(); }); + } + const auto now = crl::now(); + const auto index = state->spoiler->index(now, false); + const auto frame = Ui::DefaultImageSpoiler().frame(index); + Ui::FillSpoilerRect(p, widget->rect(), frame); + return; + } if (state->gif && state->gif->started()) { p.drawImage( 0, @@ -99,7 +138,7 @@ namespace { .outer = widget->size(), }).toImage()); } - }, widget->lifetime()); + }); const auto updateThumbnail = [=] { if (document->dimensions.isEmpty()) { @@ -130,7 +169,37 @@ namespace { }, state->loadingLifetime); } - return widget; + base::install_event_filter(widget, [=](not_null e) { + if (e->type() == QEvent::ContextMenu) { + const auto menu = Ui::CreateChild( + widget, + st::popupMenuWithIcons); + menu->addAction( + state->hasSpoiler + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now), + [=] { + state->hasSpoiler = !state->hasSpoiler; + if (!state->hasSpoiler) { + state->spoiler = nullptr; + state->firstFrame = QImage(); + state->blurredFrame = QImage(); + if (state->gif && state->gif->ready()) { + state->gif->start({ .frame = widget->size() }); + } + } + widget->update(); + }, + state->hasSpoiler + ? &st::menuIconSpoilerOff + : &st::menuIconSpoiler); + menu->popup(QCursor::pos()); + return base::EventFilterResult::Cancel; + } + return base::EventFilterResult::Continue; + }); + + return state; } [[nodiscard]] not_null AddInputField( @@ -354,12 +423,16 @@ void SendGifWithCaptionBox( const SendMenu::Details &details, Fn c) { box->setTitle(tr::lng_send_gif_with_caption()); - [[maybe_unused]] const auto gifWidget = AddGifWidget( + const auto state = AddGifWidget( box->verticalLayout(), document, st::boxWidth); Ui::AddSkip(box->verticalLayout()); - CaptionBox(box, tr::lng_send_button(), {}, peer, details, std::move(c)); + const auto d = [=](Api::SendOptions o, TextWithTags t) { + o.mediaSpoiler = state->hasSpoiler; + c(std::move(o), std::move(t)); + }; + CaptionBox(box, tr::lng_send_button(), {}, peer, details, std::move(d)); } void EditCaptionBox( diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index a1174707eb..6d79c2ccea 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -518,7 +518,8 @@ void GifsListWidget::selectInlineResult( || (media && media->image(PhotoSize::Large))) { _photoChosen.fire({ .photo = photo, - .options = options }); + .options = options + }); } else if (!photo->loading(PhotoSize::Thumbnail)) { photo->load(PhotoSize::Thumbnail, Data::FileOrigin()); } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 3a44cd34c9..529f535605 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -698,6 +698,7 @@ HistoryItem::HistoryItem( _media = std::make_unique(this, document, Args{ .hasQualitiesList = video && !video->qualities.empty(), .skipPremiumEffect = !history->session().premium(), + .spoiler = fields.mediaSpoiler, }); setText(caption); } @@ -710,7 +711,7 @@ HistoryItem::HistoryItem( : HistoryItem(history, fields) { createComponentsHelper(std::move(fields)); - const auto spoiler = false; + const auto spoiler = fields.mediaSpoiler; _media = std::make_unique(this, photo, spoiler); setText(caption); } diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index a2f088f233..b483ac7a43 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -96,6 +96,7 @@ struct HistoryItemCommonFields { HistoryMessageSuggestInfo suggest; bool ignoreForwardFrom = false; bool ignoreForwardCaptions = false; + bool mediaSpoiler = false; }; enum class HistoryReactionSource : char {