Allow editing spoiler/caption-above in EditCaptionBox.

This commit is contained in:
John Preston 2024-05-30 00:01:18 +04:00
parent 8c0351be4e
commit 974bf99921
21 changed files with 247 additions and 156 deletions

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "history/history.h" #include "history/history.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
@ -255,85 +256,85 @@ mtpRequestId EditTextMessage(
SendOptions options, SendOptions options,
Fn<void(mtpRequestId requestId)> done, Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail, Fn<void(const QString &error, mtpRequestId requestId)> fail,
std::optional<bool> spoilerMediaOverride) { bool spoilered) {
if (spoilerMediaOverride) { const auto media = item->media();
const auto spoiler = *spoilerMediaOverride; if (media
if (const auto media = item->media()) { && HistoryView::MediaEditManager::CanBeSpoilered(item)
auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr); && spoilered != media->hasSpoiler()) {
auto takeFileReference = Fn<QByteArray()>(nullptr); auto takeInputMedia = Fn<std::optional<MTPInputMedia>()>(nullptr);
if (const auto photo = media->photo()) { auto takeFileReference = Fn<QByteArray()>(nullptr);
using Flag = MTPDinputMediaPhoto::Flag; if (const auto photo = media->photo()) {
const auto flags = Flag() using Flag = MTPDinputMediaPhoto::Flag;
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag()) const auto flags = Flag()
| (spoiler ? Flag::f_spoiler : Flag()); | (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
takeInputMedia = [=] { | (spoilered ? Flag::f_spoiler : Flag());
return MTP_inputMediaPhoto( takeInputMedia = [=] {
MTP_flags(flags), return MTP_inputMediaPhoto(
photo->mtpInput(), MTP_flags(flags),
MTP_int(media->ttlSeconds())); photo->mtpInput(),
}; MTP_int(media->ttlSeconds()));
takeFileReference = [=] { return photo->fileReference(); };
} 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());
takeInputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(flags),
document->mtpInput(),
MTP_int(media->ttlSeconds()),
MTPstring()); // query
};
takeFileReference = [=] { return document->fileReference(); };
}
const auto usedFileReference = takeFileReference
? takeFileReference()
: QByteArray();
const auto origin = item->fullId();
const auto api = &item->history()->session().api();
const auto performRequest = [=](
const auto &repeatRequest,
mtpRequestId originalRequestId) -> mtpRequestId {
const auto handleReference = [=](
const QString &error,
mtpRequestId requestId) {
if (error.startsWith(u"FILE_REFERENCE_"_q)) {
api->refreshFileReference(origin, [=](const auto &) {
if (takeFileReference &&
(takeFileReference() != usedFileReference)) {
repeatRequest(
repeatRequest,
originalRequestId
? originalRequestId
: requestId);
} else {
fail(error, requestId);
}
});
} else {
fail(error, requestId);
}
};
const auto callback = [=](
Fn<void()> applyUpdates,
mtpRequestId requestId) {
applyUpdates();
done(originalRequestId ? originalRequestId : requestId);
};
const auto requestId = EditMessage(
item,
caption,
webpage,
options,
callback,
handleReference,
takeInputMedia ? takeInputMedia() : std::nullopt);
return originalRequestId ? originalRequestId : requestId;
}; };
return performRequest(performRequest, 0); takeFileReference = [=] { return photo->fileReference(); };
} else if (const auto document = media->document()) {
using Flag = MTPDinputMediaDocument::Flag;
const auto flags = Flag()
| (media->ttlSeconds() ? Flag::f_ttl_seconds : Flag())
| (spoilered ? Flag::f_spoiler : Flag());
takeInputMedia = [=] {
return MTP_inputMediaDocument(
MTP_flags(flags),
document->mtpInput(),
MTP_int(media->ttlSeconds()),
MTPstring()); // query
};
takeFileReference = [=] { return document->fileReference(); };
} }
const auto usedFileReference = takeFileReference
? takeFileReference()
: QByteArray();
const auto origin = item->fullId();
const auto api = &item->history()->session().api();
const auto performRequest = [=](
const auto &repeatRequest,
mtpRequestId originalRequestId) -> mtpRequestId {
const auto handleReference = [=](
const QString &error,
mtpRequestId requestId) {
if (error.startsWith(u"FILE_REFERENCE_"_q)) {
api->refreshFileReference(origin, [=](const auto &) {
if (takeFileReference &&
(takeFileReference() != usedFileReference)) {
repeatRequest(
repeatRequest,
originalRequestId
? originalRequestId
: requestId);
} else {
fail(error, requestId);
}
});
} else {
fail(error, requestId);
}
};
const auto callback = [=](
Fn<void()> applyUpdates,
mtpRequestId requestId) {
applyUpdates();
done(originalRequestId ? originalRequestId : requestId);
};
const auto requestId = EditMessage(
item,
caption,
webpage,
options,
callback,
handleReference,
takeInputMedia ? takeInputMedia() : std::nullopt);
return originalRequestId ? originalRequestId : requestId;
};
return performRequest(performRequest, 0);
} }
const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) { const auto callback = [=](Fn<void()> applyUpdates, mtpRequestId id) {

View file

@ -56,6 +56,6 @@ mtpRequestId EditTextMessage(
SendOptions options, SendOptions options,
Fn<void(mtpRequestId requestId)> done, Fn<void(mtpRequestId requestId)> done,
Fn<void(const QString &error, mtpRequestId requestId)> fail, Fn<void(const QString &error, mtpRequestId requestId)> fail,
std::optional<bool> spoilerMediaOverride); bool spoilered);
} // namespace Api } // namespace Api

View file

@ -514,6 +514,7 @@ void SendConfirmedFile(
edition.ttl = 0; edition.ttl = 0;
edition.mtpMedia = &media; edition.mtpMedia = &media;
edition.textWithEntities = caption; edition.textWithEntities = caption;
edition.invertMedia = file->to.options.invertCaption;
edition.useSameViews = true; edition.useSameViews = true;
edition.useSameForwards = true; edition.useSameForwards = true;
edition.useSameMarkup = true; edition.useSameMarkup = true;

View file

@ -176,7 +176,7 @@ void ChooseReplacement(
void EditPhotoImage( void EditPhotoImage(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
std::shared_ptr<Data::PhotoMedia> media, std::shared_ptr<Data::PhotoMedia> media,
bool wasSpoiler, bool spoilered,
Fn<void(Ui::PreparedList)> done) { Fn<void(Ui::PreparedList)> done) {
const auto large = media const auto large = media
? media->image(Data::PhotoSize::Large) ? media->image(Data::PhotoSize::Large)
@ -199,7 +199,7 @@ void EditPhotoImage(
using ImageInfo = Ui::PreparedFileInformation::Image; using ImageInfo = Ui::PreparedFileInformation::Image;
auto &file = list.files.front(); auto &file = list.files.front();
file.spoiler = wasSpoiler; file.spoiler = spoilered;
const auto image = std::get_if<ImageInfo>(&file.information->media); const auto image = std::get_if<ImageInfo>(&file.information->media);
image->modifications = mods; image->modifications = mods;
@ -231,13 +231,13 @@ EditCaptionBox::EditCaptionBox(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
TextWithTags &&text, TextWithTags &&text,
bool spoilered,
bool invertCaption,
Ui::PreparedList &&list, Ui::PreparedList &&list,
Fn<void()> saved) Fn<void()> saved)
: _controller(controller) : _controller(controller)
, _historyItem(item) , _historyItem(item)
, _isAllowedEditMedia(item->media() , _isAllowedEditMedia(item->media() && item->media()->allowsEditMedia())
? item->media()->allowsEditMedia()
: false)
, _albumType(ComputeAlbumType(item)) , _albumType(ComputeAlbumType(item))
, _controls(base::make_unique_q<Ui::VerticalLayout>(this)) , _controls(base::make_unique_q<Ui::VerticalLayout>(this))
, _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll)) , _scroll(base::make_unique_q<Ui::ScrollArea>(this, st::boxScroll))
@ -255,6 +255,8 @@ EditCaptionBox::EditCaptionBox(
Expects(item->media() != nullptr); Expects(item->media() != nullptr);
Expects(item->media()->allowsEditCaption()); Expects(item->media()->allowsEditCaption());
_mediaEditManager.start(item, spoilered, invertCaption);
_controller->session().data().itemRemoved( _controller->session().data().itemRemoved(
_historyItem->fullId() _historyItem->fullId()
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -268,6 +270,8 @@ void EditCaptionBox::StartMediaReplace(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
FullMsgId itemId, FullMsgId itemId,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved) { Fn<void()> saved) {
const auto session = &controller->session(); const auto session = &controller->session();
const auto item = session->data().message(itemId); const auto item = session->data().message(itemId);
@ -279,6 +283,8 @@ void EditCaptionBox::StartMediaReplace(
controller, controller,
item, item,
std::move(text), std::move(text),
spoilered,
invertCaption,
std::move(list), std::move(list),
std::move(saved))); std::move(saved)));
}; };
@ -293,6 +299,8 @@ void EditCaptionBox::StartMediaReplace(
FullMsgId itemId, FullMsgId itemId,
Ui::PreparedList &&list, Ui::PreparedList &&list,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved) { Fn<void()> saved) {
const auto session = &controller->session(); const auto session = &controller->session();
const auto item = session->data().message(itemId); const auto item = session->data().message(itemId);
@ -326,6 +334,8 @@ void EditCaptionBox::StartMediaReplace(
controller, controller,
item, item,
std::move(text), std::move(text),
spoilered,
invertCaption,
std::move(list), std::move(list),
std::move(saved))); std::move(saved)));
} }
@ -336,14 +346,15 @@ void EditCaptionBox::StartPhotoEdit(
std::shared_ptr<Data::PhotoMedia> media, std::shared_ptr<Data::PhotoMedia> media,
FullMsgId itemId, FullMsgId itemId,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved) { Fn<void()> saved) {
const auto session = &controller->session(); const auto session = &controller->session();
const auto item = session->data().message(itemId); const auto item = session->data().message(itemId);
if (!item) { if (!item) {
return; return;
} }
const auto hasSpoiler = item->media() && item->media()->hasSpoiler(); EditPhotoImage(controller, media, spoilered, [=](
EditPhotoImage(controller, media, hasSpoiler, [=](
Ui::PreparedList &&list) mutable { Ui::PreparedList &&list) mutable {
const auto item = session->data().message(itemId); const auto item = session->data().message(itemId);
if (!item) { if (!item) {
@ -353,6 +364,8 @@ void EditCaptionBox::StartPhotoEdit(
controller, controller,
item, item,
std::move(text), std::move(text),
spoilered,
invertCaption,
std::move(list), std::move(list),
std::move(saved))); std::move(saved)));
}); });
@ -363,10 +376,29 @@ void EditCaptionBox::prepare() {
addButton(tr::lng_cancel(), [=] { closeBox(); }); addButton(tr::lng_cancel(), [=] { closeBox(); });
const auto details = crl::guard(this, [=] { const auto details = crl::guard(this, [=] {
return SendMenu::Details(); auto result = SendMenu::Details();
const auto allWithSpoilers = ranges::all_of(
_preparedList.files,
&Ui::PreparedFile::spoiler);
result.spoiler = !_preparedList.hasSpoilerMenu(!_asFile)
? SendMenu::SpoilerState::None
: allWithSpoilers
? SendMenu::SpoilerState::Enabled
: SendMenu::SpoilerState::Possible;
const auto canMoveCaption = _preparedList.canMoveCaption(
false,
!_asFile
) && _field && HasSendText(_field);
result.caption = !canMoveCaption
? SendMenu::CaptionState::None
: _mediaEditManager.invertCaption()
? SendMenu::CaptionState::Above
: SendMenu::CaptionState::Below;
return result;
}); });
const auto callback = [=](SendMenu::Action action, const auto &) { const auto callback = [=](SendMenu::Action action, const auto &) {
_mediaEditManager.apply(action);
rebuildPreview();
}; };
SendMenu::SetupMenuAndShortcuts( SendMenu::SetupMenuAndShortcuts(
button, button,
@ -402,7 +434,6 @@ void EditCaptionBox::rebuildPreview() {
applyChanges(); applyChanges();
_previewHasSpoiler = nullptr;
if (_preparedList.files.empty()) { if (_preparedList.files.empty()) {
const auto media = _historyItem->media(); const auto media = _historyItem->media();
const auto photo = media->photo(); const auto photo = media->photo();
@ -436,7 +467,13 @@ void EditCaptionBox::rebuildPreview() {
_isPhoto = (media && media->isPhoto()); _isPhoto = (media && media->isPhoto());
const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType); const auto withCheckbox = _isPhoto && CanBeCompressed(_albumType);
if (media && (!withCheckbox || !_asFile)) { if (media && (!withCheckbox || !_asFile)) {
_previewHasSpoiler = [media] { return media->hasSpoiler(); }; media->spoileredChanges(
) | rpl::start_with_next([=](bool spoilered) {
_mediaEditManager.apply({ .type = spoilered
? SendMenu::ActionType::SpoilerOn
: SendMenu::ActionType::SpoilerOff
});
}, media->lifetime());
_content.reset(media); _content.reset(media);
} else { } else {
_content.reset(Ui::CreateChild<Ui::SingleFilePreview>( _content.reset(Ui::CreateChild<Ui::SingleFilePreview>(
@ -763,10 +800,7 @@ bool EditCaptionBox::setPreparedList(Ui::PreparedList &&list) {
} }
bool EditCaptionBox::hasSpoiler() const { bool EditCaptionBox::hasSpoiler() const {
return _preparedList.files.empty() return _mediaEditManager.spoilered();
? (_historyItem->media()
&& _historyItem->media()->hasSpoiler())
: _preparedList.files.front().spoiler;
} }
void EditCaptionBox::captionResized() { void EditCaptionBox::captionResized() {
@ -875,8 +909,8 @@ bool EditCaptionBox::validateLength(const QString &text) const {
} }
void EditCaptionBox::applyChanges() { void EditCaptionBox::applyChanges() {
if (!_preparedList.files.empty() && _previewHasSpoiler) { if (!_preparedList.files.empty()) {
_preparedList.files.front().spoiler = _previewHasSpoiler(); _preparedList.files.front().spoiler = _mediaEditManager.spoilered();
} }
} }
@ -905,7 +939,7 @@ void EditCaptionBox::save() {
auto options = Api::SendOptions(); auto options = Api::SendOptions();
options.scheduled = item->isScheduled() ? item->date() : 0; options.scheduled = item->isScheduled() ? item->date() : 0;
options.shortcutId = item->shortcutId(); options.shortcutId = item->shortcutId();
//options.invertCaption = _invertCaption; options.invertCaption = _mediaEditManager.invertCaption();
if (!_preparedList.files.empty()) { if (!_preparedList.files.empty()) {
if ((_albumType != Ui::AlbumType::None) if ((_albumType != Ui::AlbumType::None)

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
#include "history/view/controls/history_view_compose_media_edit_manager.h"
#include "ui/layers/box_content.h" #include "ui/layers/box_content.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
@ -37,6 +38,8 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
TextWithTags &&text, TextWithTags &&text,
bool spoilered,
bool invertCaption,
Ui::PreparedList &&list, Ui::PreparedList &&list,
Fn<void()> saved); Fn<void()> saved);
~EditCaptionBox(); ~EditCaptionBox();
@ -45,18 +48,24 @@ public:
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
FullMsgId itemId, FullMsgId itemId,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved); Fn<void()> saved);
static void StartMediaReplace( static void StartMediaReplace(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
FullMsgId itemId, FullMsgId itemId,
Ui::PreparedList &&list, Ui::PreparedList &&list,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved); Fn<void()> saved);
static void StartPhotoEdit( static void StartPhotoEdit(
not_null<Window::SessionController*> controller, not_null<Window::SessionController*> controller,
std::shared_ptr<Data::PhotoMedia> media, std::shared_ptr<Data::PhotoMedia> media,
FullMsgId itemId, FullMsgId itemId,
TextWithTags text, TextWithTags text,
bool spoilered,
bool invertCaption,
Fn<void()> saved); Fn<void()> saved);
protected: protected:
@ -107,7 +116,6 @@ private:
const base::unique_qptr<Ui::EmojiButton> _emojiToggle; const base::unique_qptr<Ui::EmojiButton> _emojiToggle;
base::unique_qptr<Ui::AbstractSinglePreview> _content; base::unique_qptr<Ui::AbstractSinglePreview> _content;
Fn<bool()> _previewHasSpoiler;
base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel; base::unique_qptr<ChatHelpers::TabbedPanel> _emojiPanel;
base::unique_qptr<QObject> _emojiFilter; base::unique_qptr<QObject> _emojiFilter;
@ -118,6 +126,7 @@ private:
std::shared_ptr<Data::PhotoMedia> _photoMedia; std::shared_ptr<Data::PhotoMedia> _photoMedia;
Ui::PreparedList _preparedList; Ui::PreparedList _preparedList;
HistoryView::MediaEditManager _mediaEditManager;
mtpRequestId _saveRequestId = 0; mtpRequestId _saveRequestId = 0;

View file

@ -595,16 +595,7 @@ bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
} }
bool SendFilesBox::hasSpoilerMenu() const { bool SendFilesBox::hasSpoilerMenu() const {
const auto allAreVideo = !ranges::any_of(_list.files, [](const auto &f) { return _list.hasSpoilerMenu(_sendWay.current().sendImagesAsPhotos());
using Type = Ui::PreparedFile::Type;
return (f.type != Type::Video);
});
const auto allAreMedia = !ranges::any_of(_list.files, [](const auto &f) {
using Type = Ui::PreparedFile::Type;
return (f.type != Type::Photo) && (f.type != Type::Video);
});
return allAreVideo
|| (allAreMedia && _sendWay.current().sendImagesAsPhotos());
} }
void SendFilesBox::applyBlockChanges() { void SendFilesBox::applyBlockChanges() {

View file

@ -328,7 +328,7 @@ HistoryWidget::HistoryWidget(
|| action.type == ActionType::CaptionDown || action.type == ActionType::CaptionDown
|| action.type == ActionType::SpoilerOn || action.type == ActionType::SpoilerOn
|| action.type == ActionType::SpoilerOff) { || action.type == ActionType::SpoilerOff) {
_mediaEditSpoiler.apply(action); _mediaEditManager.apply(action);
} else if (action.type == ActionType::Send) { } else if (action.type == ActionType::Send) {
send(action.options); send(action.options);
} else { } else {
@ -2677,7 +2677,7 @@ void HistoryWidget::setEditMsgId(MsgId msgId) {
unregisterDraftSources(); unregisterDraftSources();
_editMsgId = msgId; _editMsgId = msgId;
if (!msgId) { if (!msgId) {
_mediaEditSpoiler.cancel(); _mediaEditManager.cancel();
_canReplaceMedia = false; _canReplaceMedia = false;
if (_preview) { if (_preview) {
_preview->setDisabled(false); _preview->setDisabled(false);
@ -2749,6 +2749,8 @@ bool HistoryWidget::updateReplaceMediaButton() {
controller(), controller(),
{ _history->peer->id, _editMsgId }, { _history->peer->id, _editMsgId },
_field->getTextWithTags(), _field->getTextWithTags(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); })); crl::guard(_list, [=] { cancelEdit(); }));
}); });
}); });
@ -4065,10 +4067,10 @@ void HistoryWidget::saveEditMsg() {
item, item,
sending, sending,
webPageDraft, webPageDraft,
{ .invertCaption = _mediaEditSpoiler.invertCaption() }, { .invertCaption = _mediaEditManager.invertCaption() },
done, done,
fail, fail,
_mediaEditSpoiler.spoilered()); _mediaEditManager.spoilered());
} }
void HistoryWidget::hideChildWidgets() { void HistoryWidget::hideChildWidgets() {
@ -4228,7 +4230,7 @@ SendMenu::Details HistoryWidget::sendMenuDetails() const {
SendMenu::Details HistoryWidget::saveMenuDetails() const { SendMenu::Details HistoryWidget::saveMenuDetails() const {
return (_editMsgId && _replyEditMsg) return (_editMsgId && _replyEditMsg)
? _mediaEditSpoiler.sendMenuDetails(HasSendText(_field)) ? _mediaEditManager.sendMenuDetails(HasSendText(_field))
: SendMenu::Details(); : SendMenu::Details();
} }
@ -5613,6 +5615,8 @@ bool HistoryWidget::confirmSendingFiles(
{ _history->peer->id, _editMsgId }, { _history->peer->id, _editMsgId },
std::move(list), std::move(list),
_field->getTextWithTags(), _field->getTextWithTags(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); })); crl::guard(_list, [=] { cancelEdit(); }));
return true; return true;
} }
@ -6599,7 +6603,7 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
if (_editMsgId if (_editMsgId
&& (_inDetails || _inPhotoEdit) && (_inDetails || _inPhotoEdit)
&& (e->button() == Qt::RightButton)) { && (e->button() == Qt::RightButton)) {
_mediaEditSpoiler.showMenu( _mediaEditManager.showMenu(
_list, _list,
[=] { mouseMoveEvent(nullptr); }, [=] { mouseMoveEvent(nullptr); },
HasSendText(_field)); HasSendText(_field));
@ -6609,6 +6613,8 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
_photoEditMedia, _photoEditMedia,
{ _history->peer->id, _editMsgId }, { _history->peer->id, _editMsgId },
_field->getTextWithTags(), _field->getTextWithTags(),
_mediaEditManager.spoilered(),
_mediaEditManager.invertCaption(),
crl::guard(_list, [=] { cancelEdit(); })); crl::guard(_list, [=] { cancelEdit(); }));
} else if (!_inDetails) { } else if (!_inDetails) {
return; return;
@ -8186,7 +8192,7 @@ void HistoryWidget::updateReplyEditTexts(bool force) {
? _replyEditMsg->media() ? _replyEditMsg->media()
: nullptr; : nullptr;
if (_editMsgId && _replyEditMsg) { if (_editMsgId && _replyEditMsg) {
_mediaEditSpoiler.start(_replyEditMsg); _mediaEditManager.start(_replyEditMsg);
} }
_canReplaceMedia = editMedia && editMedia->allowsEditMedia(); _canReplaceMedia = editMedia && editMedia->allowsEditMedia();
_photoEditMedia = (_canReplaceMedia _photoEditMedia = (_canReplaceMedia
@ -8281,12 +8287,12 @@ void HistoryWidget::drawField(Painter &p, const QRect &rect) {
? drawMsgText->media() ? drawMsgText->media()
: nullptr; : nullptr;
const auto hasPreview = media && media->hasReplyPreview(); const auto hasPreview = media && media->hasReplyPreview();
const auto preview = _mediaEditSpoiler const auto preview = _mediaEditManager
? _mediaEditSpoiler.mediaPreview() ? _mediaEditManager.mediaPreview()
: hasPreview : hasPreview
? media->replyPreview() ? media->replyPreview()
: nullptr; : nullptr;
const auto spoilered = _mediaEditSpoiler.spoilered(); const auto spoilered = _mediaEditManager.spoilered();
if (!spoilered) { if (!spoilered) {
_replySpoiler = nullptr; _replySpoiler = nullptr;
} else if (!_replySpoiler) { } else if (!_replySpoiler) {

View file

@ -661,7 +661,7 @@ private:
MsgId _editMsgId = 0; MsgId _editMsgId = 0;
std::shared_ptr<Data::PhotoMedia> _photoEditMedia; std::shared_ptr<Data::PhotoMedia> _photoEditMedia;
bool _canReplaceMedia = false; bool _canReplaceMedia = false;
HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; HistoryView::MediaEditManager _mediaEditManager;
HistoryItem *_replyEditMsg = nullptr; HistoryItem *_replyEditMsg = nullptr;
Ui::Text::String _replyEditMsgText; Ui::Text::String _replyEditMsgText;

View file

@ -21,7 +21,7 @@ struct MessageToEdit {
FullMsgId fullId; FullMsgId fullId;
Api::SendOptions options; Api::SendOptions options;
TextWithTags textWithTags; TextWithTags textWithTags;
std::optional<bool> spoilerMediaOverride; bool spoilered = false;
}; };
struct VoiceToSend { struct VoiceToSend {
QByteArray bytes; QByteArray bytes;

View file

@ -216,7 +216,7 @@ private:
bool _repaintScheduled : 1 = false; bool _repaintScheduled : 1 = false;
bool _inClickable : 1 = false; bool _inClickable : 1 = false;
HistoryView::MediaEditSpoilerManager _mediaEditSpoiler; HistoryView::MediaEditManager _mediaEditManager;
const not_null<Data::Session*> _data; const not_null<Data::Session*> _data;
const not_null<Ui::IconButton*> _cancel; const not_null<Ui::IconButton*> _cancel;
@ -409,7 +409,7 @@ void FieldHeader::init() {
} }
} else if (!isLeftButton) { } else if (!isLeftButton) {
if (inPreviewRect && isEditingMessage()) { if (inPreviewRect && isEditingMessage()) {
_mediaEditSpoiler.showMenu( _mediaEditManager.showMenu(
this, this,
[=] { update(); }, [=] { update(); },
_hasSendText()); _hasSendText());
@ -587,12 +587,12 @@ void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
const auto media = _shownMessage->media(); const auto media = _shownMessage->media();
_shownMessageHasPreview = media && media->hasReplyPreview(); _shownMessageHasPreview = media && media->hasReplyPreview();
const auto preview = _mediaEditSpoiler const auto preview = _mediaEditManager
? _mediaEditSpoiler.mediaPreview() ? _mediaEditManager.mediaPreview()
: _shownMessageHasPreview : _shownMessageHasPreview
? media->replyPreview() ? media->replyPreview()
: nullptr; : nullptr;
const auto spoilered = _mediaEditSpoiler.spoilered(); const auto spoilered = _mediaEditManager.spoilered();
if (!spoilered) { if (!spoilered) {
_shownPreviewSpoiler = nullptr; _shownPreviewSpoiler = nullptr;
} else if (!_shownPreviewSpoiler) { } else if (!_shownPreviewSpoiler) {
@ -737,7 +737,7 @@ void FieldHeader::editMessage(FullMsgId id, bool photoEditAllowed) {
_photoEditAllowed = photoEditAllowed; _photoEditAllowed = photoEditAllowed;
_editMsgId = id; _editMsgId = id;
if (!photoEditAllowed) { if (!photoEditAllowed) {
_mediaEditSpoiler.cancel(); _mediaEditManager.cancel();
_inPhotoEdit = false; _inPhotoEdit = false;
_inPhotoEditOver.stop(); _inPhotoEditOver.stop();
} }
@ -784,9 +784,9 @@ MessageToEdit FieldHeader::queryToEdit() {
.options = { .options = {
.scheduled = item->isScheduled() ? item->date() : 0, .scheduled = item->isScheduled() ? item->date() : 0,
.shortcutId = item->shortcutId(), .shortcutId = item->shortcutId(),
.invertCaption = _mediaEditSpoiler.invertCaption(), .invertCaption = _mediaEditManager.invertCaption(),
}, },
.spoilerMediaOverride = _mediaEditSpoiler.spoilered(), .spoilered = _mediaEditManager.spoilered(),
}; };
} }
@ -1138,11 +1138,14 @@ bool ComposeControls::confirmMediaEdit(Ui::PreparedList &list) {
if (!isEditingMessage() || !_regularWindow) { if (!isEditingMessage() || !_regularWindow) {
return false; return false;
} else if (_canReplaceMedia) { } else if (_canReplaceMedia) {
const auto queryToEdit = _header->queryToEdit();
EditCaptionBox::StartMediaReplace( EditCaptionBox::StartMediaReplace(
_regularWindow, _regularWindow,
_editingId, _editingId,
std::move(list), std::move(list),
_field->getTextWithTags(), _field->getTextWithTags(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
} else { } else {
_show->showToast(tr::lng_edit_caption_attach(tr::now)); _show->showToast(tr::lng_edit_caption_attach(tr::now));
@ -1401,11 +1404,14 @@ void ComposeControls::init() {
_header->editPhotoRequests( _header->editPhotoRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
const auto queryToEdit = _header->queryToEdit();
EditCaptionBox::StartPhotoEdit( EditCaptionBox::StartPhotoEdit(
_regularWindow, _regularWindow,
_photoEditMedia, _photoEditMedia,
_editingId, _editingId,
_field->getTextWithTags(), _field->getTextWithTags(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
}, _wrap->lifetime()); }, _wrap->lifetime());
@ -2943,10 +2949,13 @@ bool ComposeControls::updateReplaceMediaButton() {
const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration; const auto hideDuration = st::historyReplaceMedia.ripple.hideDuration;
_replaceMedia->setClickedCallback([=] { _replaceMedia->setClickedCallback([=] {
base::call_delayed(hideDuration, _wrap.get(), [=] { base::call_delayed(hideDuration, _wrap.get(), [=] {
const auto queryToEdit = _header->queryToEdit();
EditCaptionBox::StartMediaReplace( EditCaptionBox::StartMediaReplace(
_regularWindow, _regularWindow,
_editingId, _editingId,
_field->getTextWithTags(), _field->getTextWithTags(),
queryToEdit.spoilered,
queryToEdit.options.invertCaption,
crl::guard(_wrap.get(), [=] { cancelEditMessage(); })); crl::guard(_wrap.get(), [=] { cancelEditMessage(); }));
}); });
}); });

View file

@ -21,16 +21,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView { namespace HistoryView {
MediaEditSpoilerManager::MediaEditSpoilerManager() = default; MediaEditManager::MediaEditManager() = default;
void MediaEditSpoilerManager::start(not_null<HistoryItem*> item) { void MediaEditManager::start(
not_null<HistoryItem*> item,
std::optional<bool> spoilered,
std::optional<bool> invertCaption) {
const auto media = item->media(); const auto media = item->media();
if (!media) { if (!media) {
return; return;
} }
_item = item; _item = item;
_spoilered = media->hasSpoiler(); _spoilered = spoilered.value_or(media->hasSpoiler());
_invertCaption = item->invertMedia(); _invertCaption = invertCaption.value_or(item->invertMedia());
_lifetime = item->history()->owner().itemRemoved( _lifetime = item->history()->owner().itemRemoved(
) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) { ) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
if (removed == _item) { if (removed == _item) {
@ -39,7 +42,7 @@ void MediaEditSpoilerManager::start(not_null<HistoryItem*> item) {
}); });
} }
void MediaEditSpoilerManager::apply(SendMenu::Action action) { void MediaEditManager::apply(SendMenu::Action action) {
using Type = SendMenu::Action::Type; using Type = SendMenu::Action::Type;
if (action.type == Type::CaptionUp) { if (action.type == Type::CaptionUp) {
_invertCaption = true; _invertCaption = true;
@ -52,13 +55,13 @@ void MediaEditSpoilerManager::apply(SendMenu::Action action) {
} }
} }
void MediaEditSpoilerManager::cancel() { void MediaEditManager::cancel() {
_menu = nullptr; _menu = nullptr;
_item = nullptr; _item = nullptr;
_lifetime.destroy(); _lifetime.destroy();
} }
void MediaEditSpoilerManager::showMenu( void MediaEditManager::showMenu(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
Fn<void()> finished, Fn<void()> finished,
bool hasCaptionText) { bool hasCaptionText) {
@ -88,7 +91,7 @@ void MediaEditSpoilerManager::showMenu(
_menu->popup(position); _menu->popup(position);
} }
[[nodiscard]] Image *MediaEditSpoilerManager::mediaPreview() { Image *MediaEditManager::mediaPreview() {
if (const auto media = _item ? _item->media() : nullptr) { if (const auto media = _item ? _item->media() : nullptr) {
if (const auto photo = media->photo()) { if (const auto photo = media->photo()) {
return photo->getReplyPreview( return photo->getReplyPreview(
@ -105,15 +108,15 @@ void MediaEditSpoilerManager::showMenu(
return nullptr; return nullptr;
} }
bool MediaEditSpoilerManager::spoilered() const { bool MediaEditManager::spoilered() const {
return _spoilered; return _spoilered;
} }
bool MediaEditSpoilerManager::invertCaption() const { bool MediaEditManager::invertCaption() const {
return _invertCaption; return _invertCaption;
} }
SendMenu::Details MediaEditSpoilerManager::sendMenuDetails( SendMenu::Details MediaEditManager::sendMenuDetails(
bool hasCaptionText) const { bool hasCaptionText) const {
const auto media = _item ? _item->media() : nullptr; const auto media = _item ? _item->media() : nullptr;
if (!media) { if (!media) {
@ -122,10 +125,12 @@ SendMenu::Details MediaEditSpoilerManager::sendMenuDetails(
const auto editingMedia = media->allowsEditMedia(); const auto editingMedia = media->allowsEditMedia();
const auto editPhoto = editingMedia ? media->photo() : nullptr; const auto editPhoto = editingMedia ? media->photo() : nullptr;
const auto editDocument = editingMedia ? media->document() : nullptr; const auto editDocument = editingMedia ? media->document() : nullptr;
const auto canSaveSpoiler = (editPhoto && !editPhoto->isNull()) const auto canSaveSpoiler = CanBeSpoilered(_item);
|| (editDocument const auto canMoveCaption = media->allowsEditCaption()
&& hasCaptionText
&& (editPhoto
|| editDocument
&& (editDocument->isVideoFile() || editDocument->isGifv())); && (editDocument->isVideoFile() || editDocument->isGifv()));
const auto canMoveCaption = media->allowsEditCaption() && hasCaptionText;
return { return {
.spoiler = (!canSaveSpoiler .spoiler = (!canSaveSpoiler
? SendMenu::SpoilerState::None ? SendMenu::SpoilerState::None
@ -140,4 +145,14 @@ SendMenu::Details MediaEditSpoilerManager::sendMenuDetails(
}; };
} }
bool MediaEditManager::CanBeSpoilered(not_null<HistoryItem*> item) {
const auto media = item ? item->media() : nullptr;
const auto editingMedia = media && media->allowsEditMedia();
const auto editPhoto = editingMedia ? media->photo() : nullptr;
const auto editDocument = editingMedia ? media->document() : nullptr;
return (editPhoto && !editPhoto->isNull())
|| (editDocument
&& (editDocument->isVideoFile() || editDocument->isGifv()));
}
} // namespace HistoryView } // namespace HistoryView

View file

@ -24,11 +24,14 @@ class HistoryItem;
namespace HistoryView { namespace HistoryView {
class MediaEditSpoilerManager final { class MediaEditManager final {
public: public:
MediaEditSpoilerManager(); MediaEditManager();
void start(not_null<HistoryItem*> item); void start(
not_null<HistoryItem*> item,
std::optional<bool> spoilered = {},
std::optional<bool> invertCaption = {});
void apply(SendMenu::Action action); void apply(SendMenu::Action action);
void cancel(); void cancel();
@ -49,6 +52,8 @@ public:
return _item != nullptr; return _item != nullptr;
} }
[[nodiscard]] static bool CanBeSpoilered(not_null<HistoryItem*> item);
private: private:
base::unique_qptr<Ui::PopupMenu> _menu; base::unique_qptr<Ui::PopupMenu> _menu;
HistoryItem *_item = nullptr; HistoryItem *_item = nullptr;

View file

@ -740,7 +740,7 @@ void RepliesWidget::setupComposeControls() {
_composeControls->editRequests( _composeControls->editRequests(
) | rpl::start_with_next([=](auto data) { ) | rpl::start_with_next([=](auto data) {
if (const auto item = session().data().message(data.fullId)) { if (const auto item = session().data().message(data.fullId)) {
const auto spoiler = data.spoilerMediaOverride; const auto spoiler = data.spoilered;
edit(item, data.options, saveEditMsgRequestId, spoiler); edit(item, data.options, saveEditMsgRequestId, spoiler);
} }
}, lifetime()); }, lifetime());
@ -1213,7 +1213,7 @@ void RepliesWidget::edit(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride) { bool spoilered) {
if (*saveEditMsgRequestId) { if (*saveEditMsgRequestId) {
return; return;
} }
@ -1282,7 +1282,7 @@ void RepliesWidget::edit(
options, options,
crl::guard(this, done), crl::guard(this, done),
crl::guard(this, fail), crl::guard(this, fail),
spoilerMediaOverride); spoilered);
_composeControls->hidePanelsAnimated(); _composeControls->hidePanelsAnimated();
doSetInnerFocus(); doSetInnerFocus();

View file

@ -247,7 +247,7 @@ private:
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride); bool spoilered);
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos); void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
[[nodiscard]] SendMenu::Details sendMenuDetails() const; [[nodiscard]] SendMenu::Details sendMenuDetails() const;
[[nodiscard]] FullReplyTo replyTo() const; [[nodiscard]] FullReplyTo replyTo() const;

View file

@ -320,7 +320,7 @@ void ScheduledWidget::setupComposeControls() {
) | rpl::start_with_next([=](auto data) { ) | rpl::start_with_next([=](auto data) {
if (const auto item = session().data().message(data.fullId)) { if (const auto item = session().data().message(data.fullId)) {
if (item->isScheduled()) { if (item->isScheduled()) {
const auto spoiler = data.spoilerMediaOverride; const auto spoiler = data.spoilered;
edit(item, data.options, saveEditMsgRequestId, spoiler); edit(item, data.options, saveEditMsgRequestId, spoiler);
} }
} }
@ -733,7 +733,7 @@ void ScheduledWidget::edit(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride) { bool spoilered) {
if (*saveEditMsgRequestId) { if (*saveEditMsgRequestId) {
return; return;
} }
@ -802,7 +802,7 @@ void ScheduledWidget::edit(
options, options,
crl::guard(this, done), crl::guard(this, done),
crl::guard(this, fail), crl::guard(this, fail),
spoilerMediaOverride); spoilered);
_composeControls->hidePanelsAnimated(); _composeControls->hidePanelsAnimated();
_composeControls->focus(); _composeControls->focus();

View file

@ -218,7 +218,7 @@ private:
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride); bool spoilered);
void highlightSingleNewMessage(const Data::MessagesSlice &slice); void highlightSingleNewMessage(const Data::MessagesSlice &slice);
void chooseAttach(); void chooseAttach();
[[nodiscard]] SendMenu::Details sendMenuDetails() const; [[nodiscard]] SendMenu::Details sendMenuDetails() const;

View file

@ -240,7 +240,7 @@ private:
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride); bool spoilered);
void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos); void chooseAttach(std::optional<bool> overrideSendImagesAsPhotos);
[[nodiscard]] FullReplyTo replyTo() const; [[nodiscard]] FullReplyTo replyTo() const;
void doSetInnerFocus(); void doSetInnerFocus();
@ -675,7 +675,7 @@ void ShortcutMessages::setupComposeControls() {
) | rpl::start_with_next([=](auto data) { ) | rpl::start_with_next([=](auto data) {
if (const auto item = _session->data().message(data.fullId)) { if (const auto item = _session->data().message(data.fullId)) {
if (item->isBusinessShortcut()) { if (item->isBusinessShortcut()) {
const auto spoiler = data.spoilerMediaOverride; const auto spoiler = data.spoilered;
edit(item, data.options, saveEditMsgRequestId, spoiler); edit(item, data.options, saveEditMsgRequestId, spoiler);
} }
} }
@ -1221,7 +1221,7 @@ void ShortcutMessages::edit(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
Api::SendOptions options, Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId, mtpRequestId *const saveEditMsgRequestId,
std::optional<bool> spoilerMediaOverride) { bool spoilered) {
if (*saveEditMsgRequestId) { if (*saveEditMsgRequestId) {
return; return;
} }
@ -1290,7 +1290,7 @@ void ShortcutMessages::edit(
options, options,
crl::guard(this, done), crl::guard(this, done),
crl::guard(this, fail), crl::guard(this, fail),
spoilerMediaOverride); spoilered);
_composeControls->hidePanelsAnimated(); _composeControls->hidePanelsAnimated();
doSetInnerFocus(); doSetInnerFocus();

View file

@ -78,6 +78,10 @@ bool AbstractSingleMediaPreview::canHaveSpoiler() const {
return supportsSpoilers(); return supportsSpoilers();
} }
rpl::producer<bool> AbstractSingleMediaPreview::spoileredChanges() const {
return _spoileredChanges.events();
}
void AbstractSingleMediaPreview::preparePreview(QImage preview) { void AbstractSingleMediaPreview::preparePreview(QImage preview) {
auto maxW = 0; auto maxW = 0;
auto maxH = 0; auto maxH = 0;
@ -275,6 +279,7 @@ void AbstractSingleMediaPreview::showContextMenu(QPoint position) {
? tr::lng_context_disable_spoiler(tr::now) ? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now), [=] { : tr::lng_context_spoiler_effect(tr::now), [=] {
setSpoiler(!spoilered); setSpoiler(!spoilered);
_spoileredChanges.fire_copy(!spoilered);
}, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler); }, spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
if (_menu->empty()) { if (_menu->empty()) {

View file

@ -41,6 +41,7 @@ public:
void setSpoiler(bool spoiler); void setSpoiler(bool spoiler);
[[nodiscard]] bool hasSpoiler() const; [[nodiscard]] bool hasSpoiler() const;
[[nodiscard]] bool canHaveSpoiler() const; [[nodiscard]] bool canHaveSpoiler() const;
[[nodiscard]] rpl::producer<bool> spoileredChanges() const;
protected: protected:
virtual bool supportsSpoilers() const = 0; virtual bool supportsSpoilers() const = 0;
@ -79,6 +80,7 @@ private:
int _previewHeight = 0; int _previewHeight = 0;
std::unique_ptr<SpoilerAnimation> _spoiler; std::unique_ptr<SpoilerAnimation> _spoiler;
rpl::event_stream<bool> _spoileredChanges;
const int _minThumbH; const int _minThumbH;
const base::unique_qptr<AttachControlsWidget> _controls; const base::unique_qptr<AttachControlsWidget> _controls;

View file

@ -237,6 +237,18 @@ bool PreparedList::hasSticker() const {
return ranges::any_of(files, &PreparedFile::isSticker); return ranges::any_of(files, &PreparedFile::isSticker);
} }
bool PreparedList::hasSpoilerMenu(bool compress) const {
const auto allAreVideo = !ranges::any_of(files, [](const auto &f) {
using Type = Ui::PreparedFile::Type;
return (f.type != Type::Video);
});
const auto allAreMedia = !ranges::any_of(files, [](const auto &f) {
using Type = Ui::PreparedFile::Type;
return (f.type != Type::Photo) && (f.type != Type::Video);
});
return allAreVideo || (allAreMedia && compress);
}
int MaxAlbumItems() { int MaxAlbumItems() {
return kMaxAlbumCount; return kMaxAlbumCount;
} }

View file

@ -123,6 +123,7 @@ struct PreparedList {
[[nodiscard]] bool hasSendImagesAsPhotosOption(bool slowmode) const; [[nodiscard]] bool hasSendImagesAsPhotosOption(bool slowmode) const;
[[nodiscard]] bool canHaveEditorHintLabel() const; [[nodiscard]] bool canHaveEditorHintLabel() const;
[[nodiscard]] bool hasSticker() const; [[nodiscard]] bool hasSticker() const;
[[nodiscard]] bool hasSpoilerMenu(bool compress) const;
Error error = Error::None; Error error = Error::None;
QString errorData; QString errorData;