diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9d6823fb5..4d1798501 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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"; diff --git a/Telegram/SourceFiles/api/api_common.h b/Telegram/SourceFiles/api/api_common.h index 9c3939bf5..efd92a9bc 100644 --- a/Telegram/SourceFiles/api/api_common.h +++ b/Telegram/SourceFiles/api/api_common.h @@ -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; }; diff --git a/Telegram/SourceFiles/api/api_editing.cpp b/Telegram/SourceFiles/api/api_editing.cpp index 7dcd768cb..60dfb4c69 100644 --- a/Telegram/SourceFiles/api/api_editing.cpp +++ b/Telegram/SourceFiles/api/api_editing.cpp @@ -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() diff --git a/Telegram/SourceFiles/api/api_sending.cpp b/Telegram/SourceFiles/api/api_sending.cpp index cbe617b75..8ab3d8bc7 100644 --- a/Telegram/SourceFiles/api/api_sending.cpp +++ b/Telegram/SourceFiles/api/api_sending.cpp @@ -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 diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 0e96bcec1..181cf26fb 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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 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( diff --git a/Telegram/SourceFiles/boxes/send_files_box.cpp b/Telegram/SourceFiles/boxes/send_files_box.cpp index c28a1dc70..c38004f7b 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.cpp +++ b/Telegram/SourceFiles/boxes/send_files_box.cpp @@ -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 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 { + 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(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; } diff --git a/Telegram/SourceFiles/boxes/send_files_box.h b/Telegram/SourceFiles/boxes/send_files_box.h index f6de4a4f8..1e95a1aa8 100644 --- a/Telegram/SourceFiles/boxes/send_files_box.h +++ b/Telegram/SourceFiles/boxes/send_files_box.h @@ -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 prepareSendMenuDetails( + const SendFilesBoxDescriptor &descriptor); + [[nodiscard]] auto prepareSendMenuCallback() + -> Fn; + const std::shared_ptr _show; const style::ComposeControls &_st; const Api::SendType _sendType = Api::SendType(); @@ -236,12 +245,14 @@ private: std::optional _removingIndex; SendFilesLimits _limits = {}; - Fn _sendMenuDetails = nullptr; + Fn _sendMenuDetails; + Fn _sendMenuCallback; PeerData *_captionToPeer = nullptr; SendFilesCheck _check; SendFilesConfirmed _confirmedCallback; Fn _cancelledCallback; bool _confirmed = false; + bool _invertCaption = false; object_ptr _caption = { nullptr }; TextWithTags _prefilledCaptionText; diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index d69d4e6bb..8439c5eee 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -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 }, diff --git a/Telegram/SourceFiles/data/data_message_reactions.cpp b/Telegram/SourceFiles/data/data_message_reactions.cpp index f1175a473..2b42a740d 100644 --- a/Telegram/SourceFiles/data/data_message_reactions.cpp +++ b/Telegram/SourceFiles/data/data_message_reactions.cpp @@ -1104,6 +1104,7 @@ void Reactions::defaultUpdated() { } refreshMyTags(); refreshTags(); + refreshEffects(); _defaultUpdated.fire({}); } diff --git a/Telegram/SourceFiles/media/stories/media_stories_share.cpp b/Telegram/SourceFiles/media/stories/media_stories_share.cpp index 9d32a126a..773239e0b 100644 --- a/Telegram/SourceFiles/media/stories/media_stories_share.cpp +++ b/Telegram/SourceFiles/media/stories/media_stories_share.cpp @@ -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()) { diff --git a/Telegram/SourceFiles/media/view/media_view.style b/Telegram/SourceFiles/media/view/media_view.style index 5da7f93a8..99d9b4288 100644 --- a/Telegram/SourceFiles/media/view/media_view.style +++ b/Telegram/SourceFiles/media/view/media_view.style @@ -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 }, diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 67e5deaf8..4bdd5709b 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -615,13 +615,47 @@ FillMenuResult FillSendMenu( const style::ComposeIcons *iconsOverride, std::optional 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), diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index f503a7e56..2c6dbb9b7 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -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; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp index 6ca3bd241..ebdf9ac6f 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.cpp @@ -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; diff --git a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h index c6a74790a..e467cc611 100644 --- a/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h +++ b/Telegram/SourceFiles/ui/chat/attach/attach_prepare.h @@ -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;