Added ability to change caption to every file in SendFilesBox.

This commit is contained in:
23rd
2026-02-27 20:59:55 +03:00
parent fe4f460a3b
commit 8c7d38ea25
3 changed files with 229 additions and 91 deletions
+125 -14
View File
@@ -32,6 +32,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/call_delayed.h"
#include "boxes/premium_limits_box.h"
#include "boxes/premium_preview_box.h"
#include "boxes/send_gif_with_caption_box.h"
#include "boxes/send_credits_box.h"
#include "ui/effects/scroll_content_shadow.h"
#include "ui/widgets/fields/number_input.h"
@@ -57,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/stickers/data_stickers.h"
#include "data/stickers/data_custom_emoji.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "styles/style_boxes.h"
@@ -164,6 +166,58 @@ void RenameFileBox(
});
}
void EditFileCaptionBox(
not_null<Ui::GenericBox*> box,
const style::ComposeControls &st,
PeerData *captionToPeer,
TextWithTags currentCaption,
Fn<bool(TextWithTags)> apply) {
box->setTitle(tr::lng_context_upload_edit_caption());
const auto wrap = box->addRow(
object_ptr<Ui::RpWidget>(box),
st::boxRowPadding);
const auto field = Ui::CreateChild<Ui::InputField>(
wrap,
st.files.caption,
Ui::InputField::Mode::MultiLine,
tr::lng_photo_caption());
field->setMaxLength(kMaxMessageLength);
field->setSubmitSettings(Core::App().settings().sendSubmitWay());
Ui::ResizeFitChild(wrap, field);
if (const auto window = Core::App().findWindow(box)) {
const auto controller = window->sessionController();
Ui::SetupCaptionFieldInBox(
box,
controller,
field,
captionToPeer,
[=](not_null<DocumentData*> emoji) {
return captionToPeer
&& Data::AllowEmojiWithoutPremium(captionToPeer, emoji);
},
PremiumFeature::EmojiStatus);
}
field->setTextWithTags(std::move(currentCaption));
box->setFocusCallback([=] {
field->setFocusFast();
});
const auto save = [=] {
const auto text = field->getTextWithAppliedMarkdown();
if (!apply(text)) {
return;
}
box->closeBox();
};
field->submits() | rpl::on_next([=] {
save();
}, box->lifetime());
box->addButton(tr::lng_settings_save(), save);
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
void EditPriceBox(
not_null<Ui::GenericBox*> box,
not_null<Main::Session*> session,
@@ -1449,23 +1503,80 @@ void SendFilesBox::pushBlock(int from, int till) {
if (from >= till || from >= _list.files.size()) {
return base::EventFilterResult::Continue;
}
const auto fileIndex = from;
auto fileIndex = from;
if (const auto album = dynamic_cast<Ui::AlbumPreview*>(widget)) {
const auto indexInBlock = album->indexFromPoint(mouse->pos());
if (indexInBlock < 0) {
return base::EventFilterResult::Continue;
}
fileIndex += indexInBlock;
}
if (fileIndex >= till || fileIndex >= _list.files.size()) {
return base::EventFilterResult::Continue;
}
state->menu = base::make_unique_q<Ui::PopupMenu>(
widget,
_st.tabbed.menu);
state->menu->addAction(tr::lng_rename_file(tr::now), [=] {
auto &file = _list.files[fileIndex];
_show->show(Box(RenameFileBox, file.displayName, [=](
QString newName) {
const auto displayName = std::move(newName);
_list.files[fileIndex].displayName = displayName;
if (!setDisplayNameInSingleFilePreview(
fileIndex,
displayName)) {
refreshAllAfterChanges(from);
}
}));
}, &st::menuIconEdit);
const auto &file = _list.files[fileIndex];
const auto canEditFileData
= !_sendWay.current().sendImagesAsPhotos()
|| (file.type != Ui::PreparedFile::Type::Photo
&& file.type != Ui::PreparedFile::Type::Video);
if (canEditFileData) {
state->menu->addAction(tr::lng_rename_file(tr::now), [=] {
auto &file = _list.files[fileIndex];
_show->show(Box(RenameFileBox, file.displayName, [=](
QString newName) {
const auto displayName = std::move(newName);
_list.files[fileIndex].displayName = displayName;
if (!setDisplayNameInSingleFilePreview(
fileIndex,
displayName)) {
refreshAllAfterChanges(from);
}
}));
}, &st::menuIconEdit);
state->menu->addAction(
tr::lng_context_upload_edit_caption(tr::now),
[=] {
auto &file = _list.files[fileIndex];
_show->show(Box(
EditFileCaptionBox,
_st,
_captionToPeer,
file.caption,
[=](TextWithTags text) {
if (!validateSingleCaptionLength(text.text)) {
return false;
}
auto updated = text;
const auto syncMainCaption = (fileIndex == 0)
&& _caption
&& !_caption->isHidden()
&& mainCaptionWillBeAttached();
if (fileIndex == 0) {
_mainCaptionAttachedToFirstFile = false;
_firstFileCaptionBackup = text;
}
_list.files[fileIndex].caption
= std::move(text);
if (syncMainCaption) {
_caption->setTextWithTags(updated);
}
if (!setCaptionInSingleFilePreview(
fileIndex,
updated)) {
refreshAllAfterChanges(from);
}
return true;
}));
},
&st::menuIconCaptionShow);
}
if (state->menu->empty()) {
state->menu = nullptr;
return base::EventFilterResult::Continue;
}
state->menu->popup(mouse->globalPos());
return base::EventFilterResult::Cancel;
}
@@ -53,6 +53,89 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_menu_icons.h"
namespace Ui {
void SetupCaptionFieldInBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<Ui::InputField*> field,
PeerData *panelPeer,
Fn<bool(not_null<DocumentData*>)> allowWithoutPremium,
PremiumFeature premiumFeature) {
using Limit = HistoryView::Controls::CharactersLimitLabel;
struct State final {
base::unique_qptr<ChatHelpers::TabbedPanel> emojiPanel;
base::unique_qptr<Limit> charsLimitation;
};
const auto state = box->lifetime().make_state<State>();
const auto container = box->getDelegate()->outerContainer();
using Selector = ChatHelpers::TabbedSelector;
state->emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
controller,
object_ptr<Selector>(
nullptr,
controller->uiShow(),
Window::GifPauseReason::Layer,
Selector::Mode::EmojiOnly));
const auto emojiPanel = state->emojiPanel.get();
emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(
panelPeer ? panelPeer : controller->session().user());
emojiPanel->selector()->emojiChosen(
) | rpl::on_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(field->textCursor(), data.emoji);
}, field->lifetime());
emojiPanel->selector()->customEmojiChosen(
) | rpl::on_next([=](ChatHelpers::FileChosen data) {
const auto info = data.document->sticker();
if (info
&& info->setType == Data::StickersType::Emoji
&& !allowWithoutPremium(data.document)
&& !controller->session().premium()) {
ShowPremiumPreviewBox(controller, premiumFeature);
} else {
Data::InsertCustomEmoji(field, data.document);
}
}, field->lifetime());
const auto emojiButton = Ui::AddEmojiToggleToField(
field,
box,
controller,
emojiPanel,
st::sendGifWithCaptionEmojiPosition);
emojiButton->show();
const auto session = &controller->session();
const auto checkCharsLimitation = [=](auto repeat) -> void {
const auto remove = Ui::ComputeFieldCharacterCount(field)
- Data::PremiumLimits(session).captionLengthCurrent();
if (remove > 0) {
if (!state->charsLimitation) {
state->charsLimitation = base::make_unique_q<Limit>(
field,
emojiButton,
style::al_top);
state->charsLimitation->show();
Data::AmPremiumValue(session) | rpl::on_next([=] {
repeat(repeat);
}, state->charsLimitation->lifetime());
}
state->charsLimitation->setLeft(remove);
state->charsLimitation->show();
} else {
state->charsLimitation = nullptr;
}
};
field->changes() | rpl::on_next([=] {
checkCharsLimitation(checkCharsLimitation);
}, field->lifetime());
}
namespace {
struct State final {
@@ -205,8 +288,6 @@ struct State final {
[[nodiscard]] not_null<Ui::InputField*> AddInputField(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller) {
using Limit = HistoryView::Controls::CharactersLimitLabel;
const auto bottomContainer = box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box));
const auto wrap = bottomContainer->add(
@@ -218,83 +299,15 @@ struct State final {
Ui::InputField::Mode::MultiLine,
tr::lng_photo_caption());
Ui::ResizeFitChild(wrap, input);
struct State final {
base::unique_qptr<ChatHelpers::TabbedPanel> emojiPanel;
base::unique_qptr<Limit> charsLimitation;
};
const auto state = box->lifetime().make_state<State>();
{
const auto container = box->getDelegate()->outerContainer();
using Selector = ChatHelpers::TabbedSelector;
state->emojiPanel = base::make_unique_q<ChatHelpers::TabbedPanel>(
container,
controller,
object_ptr<Selector>(
nullptr,
controller->uiShow(),
Window::GifPauseReason::Layer,
Selector::Mode::EmojiOnly));
const auto emojiPanel = state->emojiPanel.get();
emojiPanel->setDesiredHeightValues(
1.,
st::emojiPanMinHeight / 2,
st::emojiPanMinHeight);
emojiPanel->hide();
emojiPanel->selector()->setCurrentPeer(controller->session().user());
emojiPanel->selector()->emojiChosen(
) | rpl::on_next([=](ChatHelpers::EmojiChosen data) {
Ui::InsertEmojiAtCursor(input->textCursor(), data.emoji);
}, input->lifetime());
emojiPanel->selector()->customEmojiChosen(
) | rpl::on_next([=](ChatHelpers::FileChosen data) {
const auto info = data.document->sticker();
if (info
&& info->setType == Data::StickersType::Emoji
&& !controller->session().premium()) {
ShowPremiumPreviewBox(
controller,
PremiumFeature::AnimatedEmoji);
} else {
Data::InsertCustomEmoji(input, data.document);
}
}, input->lifetime());
}
const auto emojiButton = Ui::AddEmojiToggleToField(
input,
SetupCaptionFieldInBox(
box,
controller,
state->emojiPanel.get(),
st::sendGifWithCaptionEmojiPosition);
emojiButton->show();
const auto session = &controller->session();
const auto checkCharsLimitation = [=](auto repeat) -> void {
const auto remove = Ui::ComputeFieldCharacterCount(input)
- Data::PremiumLimits(session).captionLengthCurrent();
if (remove > 0) {
if (!state->charsLimitation) {
state->charsLimitation = base::make_unique_q<Limit>(
input,
emojiButton,
style::al_top);
state->charsLimitation->show();
Data::AmPremiumValue(session) | rpl::on_next([=] {
repeat(repeat);
}, state->charsLimitation->lifetime());
}
state->charsLimitation->setLeft(remove);
state->charsLimitation->show();
} else {
state->charsLimitation = nullptr;
}
};
input->changes() | rpl::on_next([=] {
checkCharsLimitation(checkCharsLimitation);
}, input->lifetime());
input,
controller->session().user(),
[](not_null<DocumentData*>) {
return false;
},
PremiumFeature::AnimatedEmoji);
return input;
}
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class PeerData;
class DocumentData;
enum class PremiumFeature;
namespace Api {
struct SendOptions;
@@ -22,9 +23,22 @@ namespace HistoryView {
class Element;
} // namespace HistoryView
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
class GenericBox;
class InputField;
void SetupCaptionFieldInBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
not_null<Ui::InputField*> field,
PeerData *panelPeer,
Fn<bool(not_null<DocumentData*>)> allowWithoutPremium,
PremiumFeature premiumFeature);
void EditCaptionBox(
not_null<Ui::GenericBox*> box,