Allow sending photo/video captions above media.

This commit is contained in:
John Preston 2024-05-29 21:27:07 +04:00
parent 924d80ecba
commit 67f7816088
15 changed files with 182 additions and 46 deletions

View file

@ -251,6 +251,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_caption_limit2#other" = "Make the caption shorter or subscribe to **Telegram Premium** to double the limit to **{count}** characters.";
"lng_caption_limit_reached#one" = "You've reached the media caption limit. Please make the caption shorter by {count} character.";
"lng_caption_limit_reached#other" = "You've reached the media caption limit. Please make the caption shorter by {count} characters.";
"lng_caption_move_up" = "Move Caption Up";
"lng_caption_move_down" = "Move Caption Down";
"lng_file_size_limit_title" = "File Too Large";
"lng_file_size_limit#one" = "{count} Gb";

View file

@ -26,6 +26,7 @@ struct SendOptions {
EffectId effectId = 0;
bool silent = false;
bool handleSupportSwitch = false;
bool invertCaption = false;
bool hideViaBot = false;
crl::time ttlSeconds = 0;
};

View file

@ -81,7 +81,8 @@ mtpRequestId EditMessage(
| ((!webpage.removed && !webpage.url.isEmpty())
? MTPmessages_EditMessage::Flag::f_media
: emptyFlag)
| ((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
| (((!webpage.removed && !webpage.url.isEmpty() && webpage.invert)
|| options.invertCaption)
? MTPmessages_EditMessage::Flag::f_invert_media
: emptyFlag)
| (!sentEntities.v.isEmpty()

View file

@ -136,6 +136,10 @@ void SendExistingMedia(
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
session->data().registerMessageRandomId(randomId, newId);
@ -314,6 +318,10 @@ bool SendDice(MessageToSend &message) {
if (action.options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
session->data().registerMessageRandomId(randomId, newId);
@ -440,6 +448,9 @@ void SendConfirmedFile(
flags |= MessageFlag::MediaIsUnread;
}
}
if (file->to.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
}
const auto messageFromId = file->to.options.sendAs
? file->to.options.sendAs->id

View file

@ -3772,7 +3772,8 @@ void ApiWrap::sendMessage(MessageToSend &&message) {
const auto anonymousPost = peer->amAnonymous();
const auto silentPost = ShouldSendSilent(peer, action.options);
FillMessagePostFlags(action, peer, flags);
if (exactWebPage && !ignoreWebPage && message.webPage.invert) {
if ((exactWebPage && !ignoreWebPage && message.webPage.invert)
|| action.options.invertCaption) {
flags |= MessageFlag::InvertMedia;
sendFlags |= MTPmessages_SendMessage::Flag::f_invert_media;
mediaFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
@ -4170,7 +4171,8 @@ void ApiWrap::sendMediaWithRandomId(
| (options.scheduled ? Flag::f_schedule_date : Flag(0))
| (options.sendAs ? Flag::f_send_as : Flag(0))
| (options.shortcutId ? Flag::f_quick_reply_shortcut : Flag(0))
| (options.effectId ? Flag::f_effect : Flag(0));
| (options.effectId ? Flag::f_effect : Flag(0))
| (options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
@ -4280,7 +4282,8 @@ void ApiWrap::sendAlbumIfReady(not_null<SendingAlbum*> album) {
| (album->options.shortcutId
? Flag::f_quick_reply_shortcut
: Flag(0))
| (album->options.effectId ? Flag::f_effect : Flag(0));
| (album->options.effectId ? Flag::f_effect : Flag(0))
| (album->options.invertCaption ? Flag::f_invert_media : Flag(0));
auto &histories = history->owner().histories();
const auto peer = history->peer;
histories.sendPreparedMessage(

View file

@ -350,9 +350,8 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
, _titleHeight(st::boxTitleHeight)
, _list(std::move(descriptor.list))
, _limits(descriptor.limits)
, _sendMenuDetails(descriptor.sendMenuDetails
? descriptor.sendMenuDetails
: [] { return SendMenu::Details(); })
, _sendMenuDetails(prepareSendMenuDetails(descriptor))
, _sendMenuCallback(prepareSendMenuCallback())
, _captionToPeer(descriptor.captionToPeer)
, _check(std::move(descriptor.check))
, _confirmedCallback(std::move(descriptor.confirmed))
@ -366,6 +365,50 @@ SendFilesBox::SendFilesBox(QWidget*, SendFilesBoxDescriptor &&descriptor)
enqueueNextPrepare();
}
Fn<SendMenu::Details()> SendFilesBox::prepareSendMenuDetails(
const SendFilesBoxDescriptor &descriptor) {
auto initial = descriptor.sendMenuDetails;
return crl::guard(this, [=] {
auto result = initial ? initial() : SendMenu::Details();
result.spoiler = !hasSpoilerMenu()
? SendMenu::SpoilerState::None
: allWithSpoilers()
? SendMenu::SpoilerState::Enabled
: SendMenu::SpoilerState::Possible;
const auto way = _sendWay.current();
const auto canMoveCaption = _list.canMoveCaption(
way.groupFiles() && way.sendImagesAsPhotos(),
way.sendImagesAsPhotos()
) && _caption && !_caption->getLastText().isEmpty();
result.caption = !canMoveCaption
? SendMenu::CaptionState::None
: _invertCaption
? SendMenu::CaptionState::Above
: SendMenu::CaptionState::Below;
return result;
});
}
auto SendFilesBox::prepareSendMenuCallback()
-> Fn<void(MenuAction, MenuDetails)> {
return crl::guard(this, [=](MenuAction action, MenuDetails details) {
using Type = SendMenu::ActionType;
switch (action.type) {
case Type::CaptionDown: _invertCaption = false; break;
case Type::CaptionUp: _invertCaption = true; break;
case Type::SpoilerOn: toggleSpoilers(true); break;
case Type::SpoilerOff: toggleSpoilers(false); break;
default:
SendMenu::DefaultCallback(
_show,
sendCallback())(
action,
details);
break;
}
});
}
void SendFilesBox::initPreview() {
using namespace rpl::mappers;
@ -533,7 +576,7 @@ void SendFilesBox::refreshButtons() {
_send,
_show,
_sendMenuDetails,
SendMenu::DefaultCallback(_show, sendCallback()));
_sendMenuCallback);
}
addButton(tr::lng_cancel(), [=] { closeBox(); });
_addFile = addLeftButton(
@ -545,8 +588,10 @@ void SendFilesBox::refreshButtons() {
addMenuButton();
}
bool SendFilesBox::hasSendMenu() const {
return (_sendMenuDetails().type != SendMenu::Type::Disabled);
bool SendFilesBox::hasSendMenu(const SendMenu::Details &details) const {
return (details.type != SendMenu::Type::Disabled)
|| (details.spoiler != SendMenu::SpoilerState::None)
|| (details.caption != SendMenu::CaptionState::None);
}
bool SendFilesBox::hasSpoilerMenu() const {
@ -583,7 +628,8 @@ void SendFilesBox::toggleSpoilers(bool enabled) {
}
void SendFilesBox::addMenuButton() {
if (!hasSendMenu() && !hasSpoilerMenu()) {
const auto details = _sendMenuDetails();
if (!hasSendMenu(details)) {
return;
}
@ -592,31 +638,16 @@ void SendFilesBox::addMenuButton() {
const auto &tabbed = _st.tabbed;
const auto &icons = tabbed.icons;
_menu = base::make_unique_q<Ui::PopupMenu>(top, tabbed.menu);
if (hasSpoilerMenu()) {
const auto spoilered = allWithSpoilers();
_menu->addAction(
(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now)),
[=] { toggleSpoilers(!spoilered); },
spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
if (hasSendMenu()) {
_menu->addSeparator(&tabbed.expandedSeparator);
}
}
if (hasSendMenu()) {
SendMenu::FillSendMenu(
_menu.get(),
_show,
_sendMenuDetails(),
SendMenu::DefaultCallback(_show, sendCallback()),
&_st.tabbed.icons,
QCursor::pos());
}
SendMenu::FillSendMenu(
_menu.get(),
_show,
_sendMenuDetails(),
_sendMenuCallback,
&_st.tabbed.icons,
QCursor::pos());
_menu->popup(QCursor::pos());
return true;
});
}
void SendFilesBox::initSendWay() {
@ -658,9 +689,7 @@ void SendFilesBox::initSendWay() {
for (auto &block : _blocks) {
block.setSendWay(value);
}
if (!hasSendMenu()) {
refreshButtons();
}
refreshButtons();
if (was != hidden()) {
updateBoxSize();
updateControlsGeometry();
@ -872,9 +901,7 @@ void SendFilesBox::pushBlock(int from, int till) {
}
void SendFilesBox::refreshControls(bool initial) {
if (initial || !hasSendMenu()) {
refreshButtons();
}
refreshButtons();
refreshTitleText();
updateSendWayControls();
updateCaptionPlaceholder();
@ -1426,9 +1453,12 @@ void SendFilesBox::send(
if ((_sendType == Api::SendType::Scheduled
|| _sendType == Api::SendType::ScheduledToUser)
&& !options.scheduled) {
auto child = _sendMenuDetails();
child.spoiler = SendMenu::SpoilerState::None;
child.caption = SendMenu::CaptionState::None;
return SendMenu::DefaultCallback(_show, sendCallback())(
{ .type = SendMenu::ActionType::Schedule },
_sendMenuDetails());
child);
}
if (_preparing) {
_whenReadySend = [=] {
@ -1453,6 +1483,7 @@ void SendFilesBox::send(
auto caption = (_caption && !_caption->isHidden())
? _caption->getTextWithAppliedMarkdown()
: TextWithTags();
options.invertCaption = _invertCaption;
if (!validateLength(caption.text)) {
return;
}

View file

@ -48,6 +48,7 @@ class SessionController;
namespace SendMenu {
struct Details;
struct Action;
} // namespace SendMenu
namespace HistoryView::Controls {
@ -136,6 +137,9 @@ protected:
void resizeEvent(QResizeEvent *e) override;
private:
using MenuAction = SendMenu::Action;
using MenuDetails = SendMenu::Details;
class Block final {
public:
Block(
@ -173,7 +177,7 @@ private:
void initSendWay();
void initPreview();
[[nodiscard]] bool hasSendMenu() const;
[[nodiscard]] bool hasSendMenu(const MenuDetails &details) const;
[[nodiscard]] bool hasSpoilerMenu() const;
[[nodiscard]] bool allWithSpoilers();
[[nodiscard]] bool checkWithWay(
@ -225,6 +229,11 @@ private:
void checkCharsLimitation();
[[nodiscard]] Fn<MenuDetails()> prepareSendMenuDetails(
const SendFilesBoxDescriptor &descriptor);
[[nodiscard]] auto prepareSendMenuCallback()
-> Fn<void(MenuAction, MenuDetails)>;
const std::shared_ptr<ChatHelpers::Show> _show;
const style::ComposeControls &_st;
const Api::SendType _sendType = Api::SendType();
@ -236,12 +245,14 @@ private:
std::optional<int> _removingIndex;
SendFilesLimits _limits = {};
Fn<SendMenu::Details()> _sendMenuDetails = nullptr;
Fn<MenuDetails()> _sendMenuDetails;
Fn<void(MenuAction, MenuDetails)> _sendMenuCallback;
PeerData *_captionToPeer = nullptr;
SendFilesCheck _check;
SendFilesConfirmed _confirmedCallback;
Fn<void()> _cancelledCallback;
bool _confirmed = false;
bool _invertCaption = false;
object_ptr<Ui::InputField> _caption = { nullptr };
TextWithTags _prefilledCaptionText;

View file

@ -69,6 +69,8 @@ ComposeIcons {
menuWhenOnline: icon;
menuSpoiler: icon;
menuSpoilerOff: icon;
menuBelow: icon;
menuAbove: icon;
stripBubble: icon;
stripExpandPanel: icon;
@ -606,6 +608,8 @@ defaultComposeIcons: ComposeIcons {
menuWhenOnline: menuIconWhenOnline;
menuSpoiler: menuIconSpoiler;
menuSpoilerOff: menuIconSpoilerOff;
menuBelow: menuIconBelow;
menuAbove: menuIconAbove;
stripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },

View file

@ -1104,6 +1104,7 @@ void Reactions::defaultUpdated() {
}
refreshMyTags();
refreshTags();
refreshEffects();
_defaultUpdated.fire({});
}

View file

@ -141,6 +141,9 @@ namespace Media::Stories {
if (options.effectId) {
sendFlags |= MTPmessages_SendMedia::Flag::f_effect;
}
if (options.invertCaption) {
sendFlags |= MTPmessages_SendMedia::Flag::f_invert_media;
}
const auto done = [=] {
if (!--state->requests) {
if (show->valid()) {

View file

@ -636,6 +636,8 @@ storiesEmojiPan: EmojiPan(defaultEmojiPan) {
menuWhenOnline: icon {{ "menu/send_when_online", storiesComposeWhiteText }};
menuSpoiler: icon {{ "menu/spoiler_on", storiesComposeWhiteText }};
menuSpoilerOff: icon {{ "menu/spoiler_off", storiesComposeWhiteText }};
menuBelow: icon {{ "menu/link_below", storiesComposeWhiteText }};
menuAbove: icon {{ "menu/link_above", storiesComposeWhiteText }};
stripBubble: icon{
{ "chat/reactions_bubble_shadow", windowShadowFg },

View file

@ -615,13 +615,47 @@ FillMenuResult FillSendMenu(
const style::ComposeIcons *iconsOverride,
std::optional<QPoint> desiredPositionOverride) {
const auto type = details.type;
if (type == Type::Disabled || !action) {
const auto empty = (type == Type::Disabled)
&& (details.spoiler == SpoilerState::None)
&& (details.caption == CaptionState::None);
if (empty || !action) {
return FillMenuResult::Skipped;
}
const auto &icons = iconsOverride
? *iconsOverride
: st::defaultComposeIcons;
auto toggles = false;
if (details.spoiler != SpoilerState::None) {
const auto spoilered = (details.spoiler == SpoilerState::Enabled);
menu->addAction(
(spoilered
? tr::lng_context_disable_spoiler(tr::now)
: tr::lng_context_spoiler_effect(tr::now)),
[=] { action({ .type = spoilered
? ActionType::SpoilerOff
: ActionType::SpoilerOn
}, details); },
spoilered ? &icons.menuSpoilerOff : &icons.menuSpoiler);
toggles = true;
}
if (details.caption != CaptionState::None) {
const auto above = (details.caption == CaptionState::Above);
menu->addAction(
(above
? tr::lng_caption_move_down(tr::now)
: tr::lng_caption_move_up(tr::now)),
[=] { action({ .type = above
? ActionType::CaptionDown
: ActionType::CaptionUp
}, details); },
above ? &icons.menuBelow : &icons.menuAbove);
toggles = true;
}
if (toggles && type != Type::Disabled) {
menu->addSeparator();
}
if (type != Type::Reminder) {
menu->addAction(
tr::lng_send_silent_message(tr::now),

View file

@ -29,7 +29,7 @@ class Thread;
namespace SendMenu {
enum class Type {
enum class Type : uchar {
Disabled,
SilentOnly,
Scheduled,
@ -37,20 +37,38 @@ enum class Type {
Reminder,
};
enum class SpoilerState : uchar {
None,
Enabled,
Possible,
};
enum class CaptionState : uchar {
None,
Below,
Above,
};
struct Details {
Type type = Type::Disabled;
SpoilerState spoiler = SpoilerState::None;
CaptionState caption = CaptionState::None;
bool effectAllowed = false;
};
enum class FillMenuResult {
enum class FillMenuResult : uchar {
Prepared,
Skipped,
Failed,
};
enum class ActionType {
enum class ActionType : uchar {
Send,
Schedule,
SpoilerOn,
SpoilerOff,
CaptionUp,
CaptionDown,
};
struct Action {
using Type = ActionType;

View file

@ -184,6 +184,17 @@ bool PreparedList::canAddCaption(bool sendingAlbum, bool compress) const {
return !hasFiles && !hasMusic && !hasNotGrouped;
}
bool PreparedList::canMoveCaption(bool sendingAlbum, bool compress) const {
if (!canAddCaption(sendingAlbum, compress)) {
return false;
} else if (files.size() != 1) {
return true;
}
const auto &file = files.front();
return (file.type == PreparedFile::Type::Video)
|| (file.type == PreparedFile::Type::Photo && compress);
}
bool PreparedList::hasGroupOption(bool slowmode) const {
if (slowmode || files.size() < 2) {
return false;

View file

@ -112,6 +112,9 @@ struct PreparedList {
void mergeToEnd(PreparedList &&other, bool cutToAlbumSize = false);
[[nodiscard]] bool canAddCaption(bool sendingAlbum, bool compress) const;
[[nodiscard]] bool canMoveCaption(
bool sendingAlbum,
bool compress) const;
[[nodiscard]] bool canBeSentInSlowmode() const;
[[nodiscard]] bool canBeSentInSlowmodeWith(
const PreparedList &other) const;