diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 2f42270ee2..88d42a190d 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -688,6 +688,8 @@ PRIVATE history/view/controls/compose_controls_common.h history/view/controls/history_view_compose_controls.cpp history/view/controls/history_view_compose_controls.h + history/view/controls/history_view_compose_media_edit_manager.cpp + history/view/controls/history_view_compose_media_edit_manager.h history/view/controls/history_view_compose_search.cpp history/view/controls/history_view_compose_search.h history/view/controls/history_view_draft_options.cpp diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index f91a73579b..5705d267ea 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -252,12 +252,47 @@ mtpRequestId EditTextMessage( Data::WebPageDraft webpage, SendOptions options, Fn done, - Fn fail) { + Fn fail, + std::optional spoilerMediaOverride) { const auto callback = [=](Fn applyUpdates, mtpRequestId id) { applyUpdates(); done(id); }; - return EditMessage(item, caption, webpage, options, callback, fail); + auto inputMedia = std::optional(); + if (spoilerMediaOverride) { + const auto spoiler = *spoilerMediaOverride; + if (const auto media = item->media()) { + if (const auto photo = media->photo()) { + using Flag = MTPDinputMediaPhoto::Flag; + const auto flags = Flag() + | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) + | (spoiler ? Flag::f_spoiler : Flag()); + inputMedia = MTP_inputMediaPhoto( + MTP_flags(flags), + photo->mtpInput(), + MTP_int(media->ttlSeconds())); + } else if (const auto document = media->document()) { + using Flag = MTPDinputMediaDocument::Flag; + const auto flags = Flag() + | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) + | (spoiler ? Flag::f_spoiler : Flag()); + inputMedia = MTP_inputMediaDocument( + MTP_flags(flags), + document->mtpInput(), + MTP_int(media->ttlSeconds()), + MTPstring()); // query + } + } + } + + return EditMessage( + item, + caption, + webpage, + options, + callback, + fail, + inputMedia); } } // namespace Api diff --git a/Telegram/SourceFiles/api/api_editing.h b/Telegram/SourceFiles/api/api_editing.h index 86f106f45e..c8d0f6c509 100644 --- a/Telegram/SourceFiles/api/api_editing.h +++ b/Telegram/SourceFiles/api/api_editing.h @@ -55,6 +55,7 @@ mtpRequestId EditTextMessage( Data::WebPageDraft webpage, SendOptions options, Fn done, - Fn fail); + Fn fail, + std::optional spoilerMediaOverride); } // namespace Api diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index fdd4935a08..5e8da7f99d 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -2656,6 +2656,7 @@ void HistoryWidget::setEditMsgId(MsgId msgId) { unregisterDraftSources(); _editMsgId = msgId; if (!msgId) { + _mediaEditSpoiler.setSpoilerOverride(std::nullopt); _canReplaceMedia = false; if (_preview) { _preview->setDisabled(false); @@ -4043,7 +4044,8 @@ void HistoryWidget::saveEditMsg() { webPageDraft, options, done, - fail); + fail, + _mediaEditSpoiler.spoilerOverride()); } void HistoryWidget::hideChildWidgets() { @@ -6562,7 +6564,14 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) { return; } const auto isReadyToForward = readyToForward(); - if (_inPhotoEdit && _photoEditMedia) { + if (_editMsgId + && (_inDetails || _inPhotoEdit) + && (e->button() == Qt::RightButton)) { + _mediaEditSpoiler.showMenu( + _list, + session().data().message(_history->peer, _editMsgId), + [=](bool) { mouseMoveEvent(nullptr); }); + } else if (_inPhotoEdit && _photoEditMedia) { EditCaptionBox::StartPhotoEdit( controller(), _photoEditMedia, @@ -8235,8 +8244,14 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) { ? drawMsgText->media() : nullptr; const auto hasPreview = media && media->hasReplyPreview(); - const auto preview = hasPreview ? media->replyPreview() : nullptr; - const auto spoilered = preview && media->hasSpoiler(); + const auto preview = _mediaEditSpoiler.spoilerOverride() + ? _mediaEditSpoiler.mediaPreview(drawMsgText) + : hasPreview + ? media->replyPreview() + : nullptr; + const auto spoilered = _mediaEditSpoiler.spoilerOverride() + ? (*_mediaEditSpoiler.spoilerOverride()) + : (preview && media->hasSpoiler()); if (!spoilered) { _replySpoiler = nullptr; } else if (!_replySpoiler) { diff --git a/Telegram/SourceFiles/history/history_widget.h b/Telegram/SourceFiles/history/history_widget.h index b4d8bb49cc..b731507b2b 100644 --- a/Telegram/SourceFiles/history/history_widget.h +++ b/Telegram/SourceFiles/history/history_widget.h @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/view/history_view_corner_buttons.h" #include "history/history_drag_area.h" #include "history/history_view_highlight_manager.h" @@ -103,6 +104,7 @@ class ForwardPanel; class TTLButton; class WebpageProcessor; class CharactersLimitLabel; +class PhotoEditSpoilerManager; } // namespace HistoryView::Controls class BotKeyboard; @@ -660,6 +662,7 @@ private: MsgId _editMsgId = 0; std::shared_ptr _photoEditMedia; bool _canReplaceMedia = false; + HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; HistoryItem *_replyEditMsg = nullptr; Ui::Text::String _replyEditMsgText; diff --git a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h index e707523ecc..01237072b6 100644 --- a/Telegram/SourceFiles/history/view/controls/compose_controls_common.h +++ b/Telegram/SourceFiles/history/view/controls/compose_controls_common.h @@ -21,6 +21,7 @@ struct MessageToEdit { FullMsgId fullId; Api::SendOptions options; TextWithTags textWithTags; + std::optional spoilerMediaOverride; }; struct VoiceToSend { QByteArray bytes; diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp index dff29122c1..9154d846ea 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.cpp @@ -50,6 +50,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/history.h" #include "history/history_item.h" #include "history/view/controls/history_view_characters_limit.h" +#include "history/view/controls/history_view_compose_media_edit_manager.h" #include "history/view/controls/history_view_forward_panel.h" #include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_voice_record_bar.h" @@ -72,6 +73,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/dropdown_menu.h" +#include "ui/widgets/popup_menu.h" #include "ui/text/format_values.h" #include "ui/controls/emoji_button.h" #include "ui/controls/send_button.h" @@ -84,6 +86,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwindow.h" #include "styles/style_chat.h" #include "styles/style_chat_helpers.h" +#include "styles/style_menu_icons.h" namespace HistoryView { namespace { @@ -210,6 +213,8 @@ private: bool _repaintScheduled : 1 = false; bool _inClickable : 1 = false; + HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; + const not_null _data; const not_null _cancel; @@ -398,7 +403,12 @@ void FieldHeader::init() { _editOptionsRequests.fire({}); } } else if (!isLeftButton) { - if (const auto reply = replyingToMessage()) { + if (inPreviewRect && isEditingMessage()) { + _mediaEditSpoiler.showMenu( + this, + _data->message(_editMsgId.current()), + [=](bool) { update(); }); + } else if (const auto reply = replyingToMessage()) { _jumpToItemRequests.fire_copy(reply); } } @@ -572,10 +582,14 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) { const auto media = _shownMessage->media(); _shownMessageHasPreview = media && media->hasReplyPreview(); - const auto preview = _shownMessageHasPreview + const auto preview = _mediaEditSpoiler.spoilerOverride() + ? _mediaEditSpoiler.mediaPreview(_shownMessage) + : _shownMessageHasPreview ? media->replyPreview() : nullptr; - const auto spoilered = preview && media->hasSpoiler(); + const auto spoilered = _mediaEditSpoiler.spoilerOverride() + ? (*_mediaEditSpoiler.spoilerOverride()) + : (preview && media->hasSpoiler()); if (!spoilered) { _shownPreviewSpoiler = nullptr; } else if (!_shownPreviewSpoiler) { @@ -720,6 +734,7 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) { _photoEditAllowed = photoEditAllowed; _editMsgId = id; if (!photoEditAllowed) { + _mediaEditSpoiler.setSpoilerOverride(std::nullopt); _inPhotoEdit = false; _inPhotoEditOver.stop(); } @@ -767,6 +782,7 @@ MessageToEdit FieldHeader::queryToEdit() { .scheduled = item->isScheduled() ? item->date() : 0, .shortcutId = item->shortcutId(), }, + .spoilerMediaOverride = _mediaEditSpoiler.spoilerOverride(), }; } @@ -3440,4 +3456,49 @@ rpl::producer SendDisabledBySlowmode(not_null peer) { _1 && _2); } +void ShowPhotoEditSpoilerMenu( + not_null parent, + not_null item, + const std::optional &override, + Fn callback) { + const auto media = item->media(); + const auto hasPreview = media && media->hasReplyPreview(); + const auto preview = hasPreview ? media->replyPreview() : nullptr; + if (!preview) { + return; + } + const auto spoilered = override + ? (*override) + : (preview && media->hasSpoiler()); + const auto menu = Ui::CreateChild( + parent, + st::popupMenuWithIcons); + menu->addAction( + spoilered + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now), + [=] { callback(!spoilered); }, + spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler); + menu->popup(QCursor::pos()); +} + +Image *MediaPreviewWithOverriddenSpoiler( + not_null item, + bool spoiler) { + if (const auto media = item->media()) { + if (const auto photo = media->photo()) { + return photo->getReplyPreview( + item->fullId(), + item->history()->peer, + spoiler); + } else if (const auto document = media->document()) { + return document->getReplyPreview( + item->fullId(), + item->history()->peer, + spoiler); + } + } + return nullptr; +} + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h index b24e0213ab..f6c915968c 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_controls.h @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; class DocumentData; class FieldAutocomplete; +class Image; namespace style { struct ComposeControls; @@ -451,4 +452,14 @@ private: [[nodiscard]] rpl::producer SendDisabledBySlowmode( not_null peer); +void ShowPhotoEditSpoilerMenu( + not_null parent, + not_null item, + const std::optional &override, + Fn callback); + +[[nodiscard]] Image *MediaPreviewWithOverriddenSpoiler( + not_null item, + bool spoiler); + } // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp new file mode 100644 index 0000000000..940ff7ff24 --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.cpp @@ -0,0 +1,83 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "history/view/controls/history_view_compose_media_edit_manager.h" + +#include "data/data_document.h" +#include "data/data_file_origin.h" +#include "data/data_photo.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "ui/widgets/popup_menu.h" +#include "styles/style_menu_icons.h" + +namespace HistoryView { + +MediaEditSpoilerManager::MediaEditSpoilerManager() = default; + +void MediaEditSpoilerManager::showMenu( + not_null parent, + not_null item, + Fn callback) { + const auto media = item->media(); + const auto hasPreview = media && media->hasReplyPreview(); + const auto preview = hasPreview ? media->replyPreview() : nullptr; + if (!preview) { + return; + } + const auto spoilered = _spoilerOverride + ? (*_spoilerOverride) + : (preview && media->hasSpoiler()); + const auto menu = Ui::CreateChild( + parent, + st::popupMenuWithIcons); + menu->addAction( + spoilered + ? tr::lng_context_disable_spoiler(tr::now) + : tr::lng_context_spoiler_effect(tr::now), + [=] { + _spoilerOverride = (!spoilered); + if (callback) { + callback(!spoilered); + } + }, + spoilered ? &st::menuIconSpoilerOff : &st::menuIconSpoiler); + menu->popup(QCursor::pos()); +} + +[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview( + not_null item) { + if (!_spoilerOverride) { + return nullptr; + } + if (const auto media = item->media()) { + if (const auto photo = media->photo()) { + return photo->getReplyPreview( + item->fullId(), + item->history()->peer, + *_spoilerOverride); + } else if (const auto document = media->document()) { + return document->getReplyPreview( + item->fullId(), + item->history()->peer, + *_spoilerOverride); + } + } + return nullptr; +} + +void MediaEditSpoilerManager::setSpoilerOverride( + std::optional spoilerOverride) { + _spoilerOverride = spoilerOverride; +} + +std::optional MediaEditSpoilerManager::spoilerOverride() const { + return _spoilerOverride; +} + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h new file mode 100644 index 0000000000..8bc34b7c4e --- /dev/null +++ b/Telegram/SourceFiles/history/view/controls/history_view_compose_media_edit_manager.h @@ -0,0 +1,39 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { +class RpWidget; +} // namespace Ui + +class Image; +class HistoryItem; + +namespace HistoryView { + +class MediaEditSpoilerManager final { +public: + MediaEditSpoilerManager(); + + void showMenu( + not_null parent, + not_null item, + Fn callback); + + [[nodiscard]] Image *mediaPreview(not_null item); + + void setSpoilerOverride(std::optional spoilerOverride); + + std::optional spoilerOverride() const; + +private: + std::optional _spoilerOverride; + +}; + +} // namespace HistoryView diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index afd77d6657..1974cce2b8 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -736,7 +736,8 @@ void RepliesWidget::setupComposeControls() { _composeControls->editRequests( ) | rpl::start_with_next([=](auto data) { if (const auto item = session().data().message(data.fullId)) { - edit(item, data.options, saveEditMsgRequestId); + const auto spoiler = data.spoilerMediaOverride; + edit(item, data.options, saveEditMsgRequestId, spoiler); } }, lifetime()); @@ -1207,7 +1208,8 @@ void RepliesWidget::send(Api::SendOptions options) { void RepliesWidget::edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId) { + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride) { if (*saveEditMsgRequestId) { return; } @@ -1275,7 +1277,8 @@ void RepliesWidget::edit( webpage, options, crl::guard(this, done), - crl::guard(this, fail)); + crl::guard(this, fail), + spoilerMediaOverride); _composeControls->hidePanelsAnimated(); doSetInnerFocus(); diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index 3da1c44f0a..33b7859b48 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -246,7 +246,8 @@ private: void edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId); + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] FullReplyTo replyTo() const; diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp index 6673474234..887d16e095 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.cpp @@ -318,7 +318,8 @@ void ScheduledWidget::setupComposeControls() { ) | rpl::start_with_next([=](auto data) { if (const auto item = session().data().message(data.fullId)) { if (item->isScheduled()) { - edit(item, data.options, saveEditMsgRequestId); + const auto spoiler = data.spoilerMediaOverride; + edit(item, data.options, saveEditMsgRequestId, spoiler); } } }, lifetime()); @@ -726,7 +727,8 @@ void ScheduledWidget::sendVoice( void ScheduledWidget::edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId) { + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride) { if (*saveEditMsgRequestId) { return; } @@ -794,7 +796,8 @@ void ScheduledWidget::edit( webpage, options, crl::guard(this, done), - crl::guard(this, fail)); + crl::guard(this, fail), + spoilerMediaOverride); _composeControls->hidePanelsAnimated(); _composeControls->focus(); diff --git a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h index 43702688fe..6685c113a4 100644 --- a/Telegram/SourceFiles/history/view/history_view_scheduled_section.h +++ b/Telegram/SourceFiles/history/view/history_view_scheduled_section.h @@ -213,7 +213,8 @@ private: void edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId); + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride); void highlightSingleNewMessage(const Data::MessagesSlice &slice); void chooseAttach(); [[nodiscard]] SendMenu::Type sendMenuType() const; diff --git a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp index efeffaac98..ca82da3298 100644 --- a/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp +++ b/Telegram/SourceFiles/settings/business/settings_shortcut_messages.cpp @@ -238,7 +238,8 @@ private: void edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId); + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride); void chooseAttach(std::optional overrideSendImagesAsPhotos); [[nodiscard]] SendMenu::Type sendMenuType() const; [[nodiscard]] FullReplyTo replyTo() const; @@ -673,7 +674,8 @@ void ShortcutMessages::setupComposeControls() { ) | rpl::start_with_next([=](auto data) { if (const auto item = _session->data().message(data.fullId)) { if (item->isBusinessShortcut()) { - edit(item, data.options, saveEditMsgRequestId); + const auto spoiler = data.spoilerMediaOverride; + edit(item, data.options, saveEditMsgRequestId, spoiler); } } }, lifetime()); @@ -1213,7 +1215,8 @@ void ShortcutMessages::send(Api::SendOptions options) { void ShortcutMessages::edit( not_null item, Api::SendOptions options, - mtpRequestId *const saveEditMsgRequestId) { + mtpRequestId *const saveEditMsgRequestId, + std::optional spoilerMediaOverride) { if (*saveEditMsgRequestId) { return; } @@ -1281,7 +1284,8 @@ void ShortcutMessages::edit( webpage, options, crl::guard(this, done), - crl::guard(this, fail)); + crl::guard(this, fail), + spoilerMediaOverride); _composeControls->hidePanelsAnimated(); doSetInnerFocus();