Allow forward+reply, options in single box.

This commit is contained in:
John Preston 2024-12-06 14:58:51 +04:00
parent d157eb0b6e
commit eb29b6bffe
26 changed files with 497 additions and 338 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 959 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -4912,6 +4912,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_forward_show_captions" = "Show captions"; "lng_forward_show_captions" = "Show captions";
"lng_forward_change_recipient" = "Change recipient"; "lng_forward_change_recipient" = "Change recipient";
"lng_forward_sender_names_removed" = "Sender names removed"; "lng_forward_sender_names_removed" = "Sender names removed";
"lng_forward_header_short" = "Forward";
"lng_forward_action_show_sender" = "Show Sender Name";
"lng_forward_action_show_senders" = "Show Sender Names";
"lng_forward_action_hide_sender" = "Hide Sender Name";
"lng_forward_action_hide_senders" = "Hide Sender Names";
"lng_forward_action_show_caption" = "Show Caption";
"lng_forward_action_show_captions" = "Show Captions";
"lng_forward_action_hide_caption" = "Hide Caption";
"lng_forward_action_hide_captions" = "Hide Captions";
"lng_forward_action_change_recipient" = "Change Recipient";
"lng_forward_action_remove" = "Do Not Forward";
"lng_passport_title" = "Telegram Passport"; "lng_passport_title" = "Telegram Passport";
"lng_passport_request1" = "{bot} requests access to your personal data"; "lng_passport_request1" = "{bot} requests access to your personal data";

View file

@ -36,6 +36,8 @@ using Options = base::flags<Option>;
namespace Data { namespace Data {
struct FileOrigin;
struct UploadState { struct UploadState {
explicit UploadState(int64 size) : size(size) { explicit UploadState(int64 size) : size(size) {
} }
@ -58,8 +60,6 @@ constexpr auto kVoiceMessageCacheTag = uint8(0x03);
constexpr auto kVideoMessageCacheTag = uint8(0x04); constexpr auto kVideoMessageCacheTag = uint8(0x04);
constexpr auto kAnimationCacheTag = uint8(0x05); constexpr auto kAnimationCacheTag = uint8(0x05);
struct FileOrigin;
} // namespace Data } // namespace Data
struct MessageGroupId { struct MessageGroupId {
@ -342,3 +342,29 @@ enum class MediaWebPageFlag : uint8 {
}; };
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; } inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
using MediaWebPageFlags = base::flags<MediaWebPageFlag>; using MediaWebPageFlags = base::flags<MediaWebPageFlag>;
namespace Data {
enum class ForwardOptions {
PreserveInfo,
NoSenderNames,
NoNamesAndCaptions,
};
struct ForwardDraft {
MessageIdsList ids;
ForwardOptions options = ForwardOptions::PreserveInfo;
friend inline auto operator<=>(
const ForwardDraft&,
const ForwardDraft&) = default;
};
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;
};
} // namespace Data

View file

@ -26,7 +26,6 @@ class HistoryMainElementDelegateMixin;
struct LanguageId; struct LanguageId;
namespace Data { namespace Data {
struct Draft; struct Draft;
class Session; class Session;
class Folder; class Folder;
@ -34,29 +33,6 @@ class ChatFilter;
struct SponsoredFrom; struct SponsoredFrom;
class SponsoredMessages; class SponsoredMessages;
class HistoryMessages; class HistoryMessages;
enum class ForwardOptions {
PreserveInfo,
NoSenderNames,
NoNamesAndCaptions,
};
struct ForwardDraft {
MessageIdsList ids;
ForwardOptions options = ForwardOptions::PreserveInfo;
friend inline auto operator<=>(
const ForwardDraft&,
const ForwardDraft&) = default;
};
using ForwardDrafts = base::flat_map<MsgId, ForwardDraft>;
struct ResolvedForwardDraft {
HistoryItemsList items;
ForwardOptions options = ForwardOptions::PreserveInfo;
};
} // namespace Data } // namespace Data
namespace Dialogs { namespace Dialogs {

View file

@ -136,6 +136,9 @@ template <typename T>
HistoryItemCommonFields fields, HistoryItemCommonFields fields,
not_null<History*> history, not_null<History*> history,
not_null<HistoryItem*> original) { not_null<HistoryItem*> original) {
if (fields.flags & MessageFlag::FakeHistoryItem) {
return fields;
}
fields.flags |= NewForwardedFlags(history->peer, fields.from, original); fields.flags |= NewForwardedFlags(history->peer, fields.from, original);
return fields; return fields;
} }
@ -509,7 +512,8 @@ HistoryItem::HistoryItem(
auto config = CreateConfig(); auto config = CreateConfig();
const auto originalMedia = original->media(); const auto originalMedia = original->media();
const auto dropForwardInfo = original->computeDropForwardedInfo(); const auto dropForwardInfo = fields.ignoreForwardFrom
|| original->computeDropForwardedInfo();
const auto topicRootId = fields.replyTo.topicRootId; const auto topicRootId = fields.replyTo.topicRootId;
config.reply.messageId = config.reply.topMessageId = topicRootId; config.reply.messageId = config.reply.topMessageId = topicRootId;
config.reply.topicPost = (topicRootId != 0) ? 1 : 0; config.reply.topicPost = (topicRootId != 0) ? 1 : 0;
@ -597,9 +601,22 @@ HistoryItem::HistoryItem(
} }
} }
setText(dropForwardInfo const auto dropText = fields.ignoreForwardCaptions
&& _media
&& (_media->photo() || _media->document())
&& !_media->webpage();
setText(dropText
? TextWithEntities()
: dropForwardInfo
? DropDisallowedCustomEmoji(history->peer, original->originalText()) ? DropDisallowedCustomEmoji(history->peer, original->originalText())
: original->originalText()); : original->originalText());
if (fields.groupedId) {
setGroupId(MessageGroupId::FromRaw(
history->peer->id,
fields.groupedId,
_flags & MessageFlag::IsOrWasScheduled));
}
} }
HistoryItem::HistoryItem( HistoryItem::HistoryItem(

View file

@ -107,6 +107,8 @@ struct HistoryItemCommonFields {
uint64 groupedId = 0; uint64 groupedId = 0;
EffectId effectId = 0; EffectId effectId = 0;
HistoryMessageMarkupData markup; HistoryMessageMarkupData markup;
bool ignoreForwardFrom = false;
bool ignoreForwardCaptions = false;
}; };
enum class HistoryReactionSource : char { enum class HistoryReactionSource : char {

View file

@ -2109,7 +2109,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
if (!_replyEditMsg) { if (!_replyEditMsg) {
requestMessageData(_editMsgId); requestMessageData(_editMsgId);
} }
} else if (!readyToForward()) { } else {
const auto draft = _history->localDraft({}); const auto draft = _history->localDraft({});
_processingReplyTo = draft ? draft->reply : FullReplyTo(); _processingReplyTo = draft ? draft->reply : FullReplyTo();
if (_processingReplyTo) { if (_processingReplyTo) {
@ -4982,7 +4982,7 @@ bool HistoryWidget::showRecordButton() const {
&& !_voiceRecordBar->isRecordingByAnotherBar() && !_voiceRecordBar->isRecordingByAnotherBar()
&& !HasSendText(_field) && !HasSendText(_field)
&& !_previewDrawPreview && !_previewDrawPreview
&& !readyToForward() && (_replyTo || !readyToForward())
&& !_editMsgId; && !_editMsgId;
} }
@ -6825,18 +6825,15 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
_peer, _peer,
Window::SectionShow::Way::Forward, Window::SectionShow::Way::Forward,
_editMsgId); _editMsgId);
} else if (isReadyToForward) {
if (e->button() != Qt::LeftButton) {
_forwardPanel->editToNextOption();
} else {
_forwardPanel->editOptions(controller()->uiShow());
}
} else if (_replyTo } else if (_replyTo
&& ((e->modifiers() & Qt::ControlModifier) && ((e->modifiers() & Qt::ControlModifier)
|| (e->button() != Qt::LeftButton))) { || (e->button() != Qt::LeftButton))) {
jumpToReply(_replyTo); jumpToReply(_replyTo);
} else if (_replyTo) { } else if (_replyTo
|| (isReadyToForward && e->button() == Qt::LeftButton)) {
editDraftOptions(); editDraftOptions();
} else if (isReadyToForward) {
_forwardPanel->editToNextOption();
} else if (_kbReplyTo) { } else if (_kbReplyTo) {
controller()->showPeerHistory( controller()->showPeerHistory(
_kbReplyTo->history()->peer->id, _kbReplyTo->history()->peer->id,
@ -6851,15 +6848,18 @@ void HistoryWidget::editDraftOptions() {
const auto history = _history; const auto history = _history;
const auto reply = _replyTo; const auto reply = _replyTo;
const auto webpage = _preview->draft(); const auto webpage = _preview->draft();
const auto forward = _forwardPanel->draft();
const auto done = [=]( const auto done = [=](
FullReplyTo replyTo, FullReplyTo replyTo,
Data::WebPageDraft webpage) { Data::WebPageDraft webpage,
Data::ForwardDraft forward) {
if (replyTo) { if (replyTo) {
replyToMessage(replyTo); replyToMessage(replyTo);
} else { } else {
cancelReply(); cancelReply();
} }
history->setForwardDraft({}, std::move(forward));
_preview->apply(webpage); _preview->apply(webpage);
}; };
const auto replyToId = reply.messageId; const auto replyToId = reply.messageId;
@ -6873,6 +6873,7 @@ void HistoryWidget::editDraftOptions() {
.history = history, .history = history,
.draft = Data::Draft(_field, reply, _preview->draft()), .draft = Data::Draft(_field, reply, _preview->draft()),
.usedLink = _preview->link(), .usedLink = _preview->link(),
.forward = _forwardPanel->draft(),
.links = _preview->links(), .links = _preview->links(),
.resolver = _preview->resolver(), .resolver = _preview->resolver(),
.done = done, .done = done,
@ -7956,7 +7957,7 @@ void HistoryWidget::processReply() {
{ .ids = { 1, itemId } }); { .ids = { 1, itemId } });
}), }),
.confirmText = tr::lng_selected_forward(), .confirmText = tr::lng_selected_forward(),
})); }));
} }
return processCancel(); return processCancel();
#endif #endif
@ -7985,7 +7986,6 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
return; return;
} }
_history->setForwardDraft(MsgId(), {});
if (_composeSearch) { if (_composeSearch) {
_composeSearch->hideAnimated(); _composeSearch->hideAnimated();
} }
@ -8243,10 +8243,10 @@ void HistoryWidget::cancelFieldAreaState() {
_preview->apply({ .removed = true }); _preview->apply({ .removed = true });
} else if (_editMsgId) { } else if (_editMsgId) {
cancelEdit(); cancelEdit();
} else if (readyToForward()) {
_history->setForwardDraft(MsgId(), {});
} else if (_replyTo) { } else if (_replyTo) {
cancelReply(); cancelReply();
} else if (readyToForward()) {
_history->setForwardDraft(MsgId(), {});
} else if (_kbReplyTo) { } else if (_kbReplyTo) {
toggleKeyboard(); toggleKeyboard();
} }
@ -8595,9 +8595,6 @@ void HistoryWidget::updateForwarding() {
_forwardPanel->update(_history, _history _forwardPanel->update(_history, _history
? _history->resolveForwardDraft(MsgId()) ? _history->resolveForwardDraft(MsgId())
: Data::ResolvedForwardDraft()); : Data::ResolvedForwardDraft());
if (readyToForward()) {
cancelReply();
}
updateControlsVisibility(); updateControlsVisibility();
updateControlsGeometry(); updateControlsGeometry();
} }

View file

@ -146,6 +146,7 @@ public:
[[nodiscard]] bool isEditingMessage() const; [[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] bool readyToForward() const; [[nodiscard]] bool readyToForward() const;
[[nodiscard]] const HistoryItemsList &forwardItems() const; [[nodiscard]] const HistoryItemsList &forwardItems() const;
[[nodiscard]] const Data::ResolvedForwardDraft &forwardDraft() const;
[[nodiscard]] FullReplyTo replyingToMessage() const; [[nodiscard]] FullReplyTo replyingToMessage() const;
[[nodiscard]] FullMsgId editMsgId() const; [[nodiscard]] FullMsgId editMsgId() const;
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const; [[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
@ -281,23 +282,23 @@ void FieldHeader::init() {
st::historyLinkIcon.paint(p, position, width()); st::historyLinkIcon.paint(p, position, width());
} else if (isEditingMessage()) { } else if (isEditingMessage()) {
st::historyEditIcon.paint(p, position, width()); st::historyEditIcon.paint(p, position, width());
} else if (readyToForward()) {
st::historyForwardIcon.paint(p, position, width());
} else if (const auto reply = replyingToMessage()) { } else if (const auto reply = replyingToMessage()) {
if (!reply.quote.empty()) { if (!reply.quote.empty()) {
st::historyQuoteIcon.paint(p, position, width()); st::historyQuoteIcon.paint(p, position, width());
} else { } else {
st::historyReplyIcon.paint(p, position, width()); st::historyReplyIcon.paint(p, position, width());
} }
} else if (readyToForward()) {
st::historyForwardIcon.paint(p, position, width());
} }
if (_preview.parsed) { if (_preview.parsed) {
paintWebPage( paintWebPage(
p, p,
_history ? _history->peer : _data->session().user()); _history ? _history->peer : _data->session().user());
} else if (isEditingMessage() || !readyToForward()) { } else if (isEditingMessage() || replyingToMessage()) {
paintEditOrReplyToMessage(p); paintEditOrReplyToMessage(p);
} else { } else if (readyToForward()) {
paintForwardInfo(p); paintForwardInfo(p);
} }
}, lifetime()); }, lifetime());
@ -339,10 +340,10 @@ void FieldHeader::init() {
_previewCancelled.fire({}); _previewCancelled.fire({});
} else if (_editMsgId.current()) { } else if (_editMsgId.current()) {
_editCancelled.fire({}); _editCancelled.fire({});
} else if (readyToForward()) {
_forwardCancelled.fire({});
} else if (_replyTo.current()) { } else if (_replyTo.current()) {
_replyCancelled.fire({}); _replyCancelled.fire({});
} else if (readyToForward()) {
_forwardCancelled.fire({});
} }
updateVisible(); updateVisible();
update(); update();
@ -403,12 +404,9 @@ void FieldHeader::init() {
_jumpToItemRequests.fire(FullReplyTo{ _jumpToItemRequests.fire(FullReplyTo{
.messageId = _editMsgId.current() .messageId = _editMsgId.current()
}); });
} else if (readyToForward()) { } else if (reply && (e->modifiers() & Qt::ControlModifier)) {
_forwardPanel->editOptions(_show);
} else if (reply
&& (e->modifiers() & Qt::ControlModifier)) {
_jumpToItemRequests.fire_copy(reply); _jumpToItemRequests.fire_copy(reply);
} else if (reply) { } else if (reply || readyToForward()) {
_editOptionsRequests.fire({}); _editOptionsRequests.fire({});
} }
} else if (!isLeftButton) { } else if (!isLeftButton) {
@ -419,6 +417,8 @@ void FieldHeader::init() {
_hasSendText()); _hasSendText());
} else if (const auto reply = replyingToMessage()) { } else if (const auto reply = replyingToMessage()) {
_jumpToItemRequests.fire_copy(reply); _jumpToItemRequests.fire_copy(reply);
} else if (readyToForward()) {
_forwardPanel->editToNextOption();
} }
} }
} }
@ -713,6 +713,10 @@ const HistoryItemsList &FieldHeader::forwardItems() const {
return _forwardPanel->items(); return _forwardPanel->items();
} }
const Data::ResolvedForwardDraft &FieldHeader::forwardDraft() const {
return _forwardPanel->draft();
}
FullReplyTo FieldHeader::replyingToMessage() const { FullReplyTo FieldHeader::replyingToMessage() const {
return _replyTo.current(); return _replyTo.current();
} }
@ -764,9 +768,6 @@ void FieldHeader::updateForwarding(
Data::Thread *thread, Data::Thread *thread,
Data::ResolvedForwardDraft items) { Data::ResolvedForwardDraft items) {
_forwardPanel->update(thread, std::move(items)); _forwardPanel->update(thread, std::move(items));
if (readyToForward()) {
replyToMessage({});
}
updateControlsGeometry(size()); updateControlsGeometry(size());
} }
@ -1408,12 +1409,14 @@ void ComposeControls::init() {
const auto done = [=]( const auto done = [=](
FullReplyTo replyTo, FullReplyTo replyTo,
Data::WebPageDraft webpage) { Data::WebPageDraft webpage,
Data::ForwardDraft forward) {
if (replyTo) { if (replyTo) {
replyToMessage(replyTo); replyToMessage(replyTo);
} else { } else {
cancelReplyMessage(); cancelReplyMessage();
} }
history->setForwardDraft(topicRootId, std::move(forward));
_preview->apply(webpage); _preview->apply(webpage);
_field->setFocus(); _field->setFocus();
}; };
@ -1428,6 +1431,7 @@ void ComposeControls::init() {
.history = history, .history = history,
.draft = Data::Draft(_field, reply, _preview->draft()), .draft = Data::Draft(_field, reply, _preview->draft()),
.usedLink = _preview->link(), .usedLink = _preview->link(),
.forward = _header->forwardDraft(),
.links = _preview->links(), .links = _preview->links(),
.resolver = _preview->resolver(), .resolver = _preview->resolver(),
.done = done, .done = done,
@ -2013,9 +2017,6 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
_canReplaceMedia = _canAddMedia = false; _canReplaceMedia = _canAddMedia = false;
_photoEditMedia = nullptr; _photoEditMedia = nullptr;
_header->replyToMessage(draft->reply); _header->replyToMessage(draft->reply);
if (_header->replyingToMessage()) {
cancelForward();
}
_header->editMessage({}); _header->editMessage({});
if (_preview) { if (_preview) {
_preview->setDisabled(false); _preview->setDisabled(false);
@ -3018,9 +3019,6 @@ void ComposeControls::replyToMessage(FullReplyTo id) {
} }
} else { } else {
_header->replyToMessage(id); _header->replyToMessage(id);
if (_header->replyingToMessage()) {
cancelForward();
}
} }
_saveDraftText = true; _saveDraftText = true;
@ -3071,12 +3069,12 @@ bool ComposeControls::handleCancelRequest() {
} else if (isEditingMessage()) { } else if (isEditingMessage()) {
maybeCancelEditMessage(); maybeCancelEditMessage();
return true; return true;
} else if (readyToForward()) {
cancelForward();
return true;
} else if (replyingToMessage()) { } else if (replyingToMessage()) {
cancelReplyMessage(); cancelReplyMessage();
return true; return true;
} else if (readyToForward()) {
cancelForward();
return true;
} }
return false; return false;
} }

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "history/view/controls/history_view_draft_options.h" #include "history/view/controls/history_view_draft_options.h"
#include "base/random.h"
#include "base/timer_rpl.h" #include "base/timer_rpl.h"
#include "base/unixtime.h" #include "base/unixtime.h"
#include "boxes/filters/edit_filter_chats_list.h" #include "boxes/filters/edit_filter_chats_list.h"
@ -20,12 +21,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_thread.h" #include "data/data_thread.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "history/view/controls/history_view_forward_panel.h"
#include "history/view/controls/history_view_webpage_processor.h" #include "history/view/controls/history_view_webpage_processor.h"
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
#include "history/view/history_view_cursor_state.h" #include "history/view/history_view_cursor_state.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item_helpers.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
@ -41,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/ui_utility.h" #include "ui/ui_utility.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/section_widget.h" #include "window/section_widget.h"
#include "window/window_peer_menu.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
@ -56,6 +60,7 @@ namespace {
enum class Section { enum class Section {
Reply, Reply,
Forward,
Link, Link,
}; };
@ -104,6 +109,10 @@ public:
not_null<History*> history); not_null<History*> history);
~PreviewWrap(); ~PreviewWrap();
[[nodiscard]] bool hasViewForItem(
not_null<const HistoryItem*> item) const;
void showForwardSelector(Data::ResolvedForwardDraft draft);
[[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector( [[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector(
const SelectedQuote &quote); const SelectedQuote &quote);
[[nodiscard]] rpl::producer<QString> showLinkSelector( [[nodiscard]] rpl::producer<QString> showLinkSelector(
@ -117,6 +126,11 @@ public:
} }
private: private:
struct Entry {
HistoryItem *item = nullptr;
std::unique_ptr<Element> view;
};
void paintEvent(QPaintEvent *e) override; void paintEvent(QPaintEvent *e) override;
void leaveEventHook(QEvent *e) override; void leaveEventHook(QEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override; void mouseMoveEvent(QMouseEvent *e) override;
@ -129,7 +143,8 @@ private:
_visibleBottom = bottom; _visibleBottom = bottom;
} }
void initElement(); void clear(std::vector<Entry> entries);
void initElements();
void highlightUsedLink( void highlightUsedLink(
const TextWithTags &message, const TextWithTags &message,
const QString &usedLink, const QString &usedLink,
@ -144,8 +159,8 @@ private:
const std::unique_ptr<PreviewDelegate> _delegate; const std::unique_ptr<PreviewDelegate> _delegate;
Section _section = Section::Reply; Section _section = Section::Reply;
HistoryItem *_draftItem = nullptr; std::vector<Entry> _entries;
std::unique_ptr<Element> _element; base::flat_set<not_null<const Element*>> _views;
rpl::variable<TextSelection> _selection; rpl::variable<TextSelection> _selection;
rpl::event_stream<QString> _chosenUrl; rpl::event_stream<QString> _chosenUrl;
Ui::PeerUserpicView _userpic; Ui::PeerUserpicView _userpic;
@ -191,7 +206,7 @@ PreviewWrap::PreviewWrap(
const auto session = &_history->session(); const auto session = &_history->session();
session->data().viewRepaintRequest( session->data().viewRepaintRequest(
) | rpl::start_with_next([=](not_null<const Element*> view) { ) | rpl::start_with_next([=](not_null<const Element*> view) {
if (view == _element.get()) { if (_views.contains(view)) {
update(); update();
} }
}, lifetime()); }, lifetime());
@ -221,26 +236,92 @@ PreviewWrap::PreviewWrap(
PreviewWrap::~PreviewWrap() { PreviewWrap::~PreviewWrap() {
_selection.reset(TextSelection()); _selection.reset(TextSelection());
base::take(_views);
clear(base::take(_entries));
}
void PreviewWrap::clear(std::vector<Entry> entries) {
_elementLifetime.destroy(); _elementLifetime.destroy();
_element = nullptr; for (auto &entry : entries) {
if (_draftItem) { entry.view = nullptr;
_draftItem->destroy(); if (const auto item = entry.item) {
item->destroy();
}
} }
} }
bool PreviewWrap::hasViewForItem(not_null<const HistoryItem*> item) const {
return (item->history() == _history)
&& ranges::contains(_views, item, &Element::data);
}
void PreviewWrap::showForwardSelector(Data::ResolvedForwardDraft draft) {
Expects(!draft.items.empty());
_selection.reset(TextSelection());
auto was = base::take(_entries);
auto groups = base::flat_map<MessageGroupId, uint64>();
const auto groupByItem = [&](not_null<HistoryItem*> item) {
const auto groupId = item->groupId();
if (!groupId) {
return uint64();
}
auto i = groups.find(groupId);
if (i == end(groups)) {
i = groups.emplace(groupId, base::RandomValue<uint64>()).first;
}
return i->second;
};
const auto wasViews = base::take(_views);
using Options = Data::ForwardOptions;
const auto dropNames = (draft.options != Options::PreserveInfo);
const auto dropCaptions = (draft.options == Options::NoNamesAndCaptions);
for (const auto &source : draft.items) {
const auto groupedId = groupByItem(source);
const auto item = _history->addNewLocalMessage({
.id = _history->nextNonHistoryEntryId(),
.flags = (MessageFlag::FakeHistoryItem
| MessageFlag::Outgoing
| MessageFlag::HasFromId
| (source->invertMedia()
? MessageFlag::InvertMedia
: MessageFlag())),
.from = _history->session().userPeerId(),
.date = base::unixtime::now(),
.groupedId = groupedId,
.ignoreForwardFrom = dropNames,
.ignoreForwardCaptions = dropCaptions,
}, source);
_entries.push_back({ item });
}
for (auto &entry : _entries) {
entry.view = entry.item->createView(_delegate.get());
_views.emplace(entry.view.get());
}
_link = _pressedLink = nullptr;
clear(std::move(was));
_section = Section::Forward;
initElements();
}
rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector( rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector(
const SelectedQuote &quote) { const SelectedQuote &quote) {
_selection.reset(TextSelection()); _selection.reset(TextSelection());
auto was = base::take(_entries);
const auto wasViews = base::take(_views);
const auto item = quote.item; const auto item = quote.item;
const auto group = item->history()->owner().groups().find(item); const auto group = item->history()->owner().groups().find(item);
const auto leader = group ? group->items.front().get() : item; const auto leader = group ? group->items.front().get() : item;
_element = leader->createView(_delegate.get()); _entries.push_back({
.view = leader->createView(_delegate.get()),
});
_views.emplace(_entries.back().view.get());
_link = _pressedLink = nullptr; _link = _pressedLink = nullptr;
clear(std::move(was));
if (const auto was = base::take(_draftItem)) {
was->destroy();
}
const auto media = item->media(); const auto media = item->media();
_onlyMessageText = media _onlyMessageText = media
@ -249,12 +330,13 @@ rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector(
|| (!media->photo() && !media->document())); || (!media->photo() && !media->document()));
_section = Section::Reply; _section = Section::Reply;
initElement(); initElements();
_selection = _element->selectionFromQuote(quote); const auto view = _entries.back().view.get();
_selection = view->selectionFromQuote(quote);
return _selection.value( return _selection.value(
) | rpl::map([=](TextSelection selection) { ) | rpl::map([=](TextSelection selection) {
if (const auto result = _element->selectedQuote(selection)) { if (const auto result = view->selectedQuote(selection)) {
return result; return result;
} }
return SelectedQuote{ item }; return SelectedQuote{ item };
@ -267,13 +349,11 @@ rpl::producer<QString> PreviewWrap::showLinkSelector(
const std::vector<MessageLinkRange> &links, const std::vector<MessageLinkRange> &links,
const QString &usedLink) { const QString &usedLink) {
_selection.reset(TextSelection()); _selection.reset(TextSelection());
base::take(_views);
clear(base::take(_entries));
_element = nullptr;
if (const auto was = base::take(_draftItem)) {
was->destroy();
}
using Flag = MTPDmessageMediaWebPage::Flag; using Flag = MTPDmessageMediaWebPage::Flag;
_draftItem = _history->addNewLocalMessage({ const auto item = _history->addNewLocalMessage({
.id = _history->nextNonHistoryEntryId(), .id = _history->nextNonHistoryEntryId(),
.flags = (MessageFlag::FakeHistoryItem .flags = (MessageFlag::FakeHistoryItem
| MessageFlag::Outgoing | MessageFlag::Outgoing
@ -299,13 +379,15 @@ rpl::producer<QString> PreviewWrap::showLinkSelector(
MTP_long(webpage.id), MTP_long(webpage.id),
MTP_string(webpage.url), MTP_string(webpage.url),
MTP_int(0)))); MTP_int(0))));
_element = _draftItem->createView(_delegate.get()); _entries.push_back({ item, item->createView(_delegate.get()) });
_views.emplace(_entries.back().view.get());
_selectType = TextSelectType::Letters; _selectType = TextSelectType::Letters;
_symbol = _selectionStartSymbol = 0; _symbol = _selectionStartSymbol = 0;
_afterSymbol = _selectionStartAfterSymbol = false; _afterSymbol = _selectionStartAfterSymbol = false;
_section = Section::Link; _section = Section::Link;
initElement(); initElements();
highlightUsedLink(message, usedLink, links); highlightUsedLink(message, usedLink, links);
return _chosenUrl.events(); return _chosenUrl.events();
@ -338,7 +420,8 @@ void PreviewWrap::highlightUsedLink(
text = text.mid(0, text.size() - 1); text = text.mid(0, text.size() - 1);
--selection.to; --selection.to;
} }
const auto basic = _element->textState(QPoint(0, 0), { const auto view = _entries.back().view.get();
const auto basic = view->textState(QPoint(0, 0), {
.flags = Ui::Text::StateRequest::Flag::LookupSymbol, .flags = Ui::Text::StateRequest::Flag::LookupSymbol,
.onlyMessageText = true, .onlyMessageText = true,
}); });
@ -353,30 +436,31 @@ void PreviewWrap::highlightUsedLink(
} }
void PreviewWrap::paintEvent(QPaintEvent *e) { void PreviewWrap::paintEvent(QPaintEvent *e) {
if (!_element) {
return;
}
auto p = Painter(this); auto p = Painter(this);
p.translate(_position);
auto context = _theme->preparePaintContext( auto context = _theme->preparePaintContext(
_style.get(), _style.get(),
rect(), rect(),
e->rect(), e->rect(),
!window()->isActiveWindow()); !window()->isActiveWindow());
context.outbg = _element->hasOutLayout(); for (const auto &entry : _entries) {
context.selection = _selecting context.outbg = entry.view->hasOutLayout();
? resolveNewSelection() context.selection = _selecting
: _selection.current(); ? resolveNewSelection()
: _selection.current();
p.translate(_position); entry.view->draw(p, context);
_element->draw(p, context);
if (_element->displayFromPhoto()) { p.translate(0, entry.view->height());
}
const auto top = _entries.empty() ? nullptr : _entries.back().view.get();
if (top && top->displayFromPhoto()) {
auto userpicBottom = height() auto userpicBottom = height()
- _element->marginBottom() - top->marginBottom()
- _element->marginTop(); - top->marginTop();
const auto item = _element->data(); const auto item = top->data();
const auto userpicTop = userpicBottom - st::msgPhotoSize; const auto userpicTop = userpicBottom - st::msgPhotoSize;
if (const auto from = item->displayFrom()) { if (const auto from = item->displayFrom()) {
from->paintUserpicLeft( from->paintUserpicLeft(
@ -415,7 +499,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
} }
void PreviewWrap::leaveEventHook(QEvent *e) { void PreviewWrap::leaveEventHook(QEvent *e) {
if (!_element || !_over) { if (!_over) {
return; return;
} }
_over = false; _over = false;
@ -427,7 +511,7 @@ void PreviewWrap::leaveEventHook(QEvent *e) {
} }
void PreviewWrap::mouseMoveEvent(QMouseEvent *e) { void PreviewWrap::mouseMoveEvent(QMouseEvent *e) {
if (!_element) { if (_entries.empty()) {
return; return;
} }
using Flag = Ui::Text::StateRequest::Flag; using Flag = Ui::Text::StateRequest::Flag;
@ -438,7 +522,16 @@ void PreviewWrap::mouseMoveEvent(QMouseEvent *e) {
.onlyMessageText = (_section == Section::Link || _onlyMessageText), .onlyMessageText = (_section == Section::Link || _onlyMessageText),
}; };
const auto position = e->pos(); const auto position = e->pos();
auto resolved = _element->textState(position - _position, request); auto local = position - _position;
auto resolved = TextState();
for (auto &entry : _entries) {
const auto height = entry.view->height();
if (local.y() < height) {
resolved = entry.view->textState(local, request);
break;
}
local.setY(local.y() - height);
}
_over = true; _over = true;
const auto text = (_section == Section::Reply) const auto text = (_section == Section::Reply)
&& (resolved.cursor == CursorState::Text); && (resolved.cursor == CursorState::Text);
@ -518,27 +611,25 @@ void PreviewWrap::mouseDoubleClickEvent(QMouseEvent *e) {
} }
} }
void PreviewWrap::initElement() { void PreviewWrap::initElements() {
_elementLifetime.destroy(); for (auto &entry : _entries) {
entry.view->initDimensions();
if (!_element) {
return;
} }
_element->initDimensions();
widthValue( widthValue(
) | rpl::filter([=](int width) { ) | rpl::filter([=](int width) {
return width > st::msgMinWidth; return width > st::msgMinWidth;
}) | rpl::start_with_next([=](int width) { }) | rpl::start_with_next([=](int width) {
const auto height = _position.y() auto height = _position.y();
+ _element->resizeGetHeight(width) for (const auto &entry : _entries) {
+ st::msgMargin.top(); height += entry.view->resizeGetHeight(width);
}
height += st::msgMargin.top();
resize(width, height); resize(width, height);
}, _elementLifetime); }, _elementLifetime);
} }
TextSelection PreviewWrap::resolveNewSelection() const { TextSelection PreviewWrap::resolveNewSelection() const {
if (_section != Section::Reply) { if (_section != Section::Reply || _entries.empty()) {
return TextSelection(); return TextSelection();
} }
const auto make = [](uint16 symbol, bool afterSymbol) { const auto make = [](uint16 symbol, bool afterSymbol) {
@ -551,7 +642,7 @@ TextSelection PreviewWrap::resolveNewSelection() const {
const auto result = (first <= second) const auto result = (first <= second)
? TextSelection{ first, second } ? TextSelection{ first, second }
: TextSelection{ second, first }; : TextSelection{ second, first };
return _element->adjustSelection(result, _selectType); return _entries.back().view->adjustSelection(result, _selectType);
} }
void PreviewWrap::startSelection(TextSelectType type) { void PreviewWrap::startSelection(TextSelectType type) {
@ -607,9 +698,10 @@ void DraftOptionsBox(
const auto &draft = args.draft; const auto &draft = args.draft;
struct State { struct State {
rpl::variable<Section> shown; rpl::variable<Section> shown = Section::Link;
rpl::lifetime shownLifetime; rpl::lifetime shownLifetime;
rpl::variable<SelectedQuote> quote; rpl::variable<SelectedQuote> quote;
Data::ResolvedForwardDraft forward;
Data::WebPageDraft webpage; Data::WebPageDraft webpage;
WebPageData *preview = nullptr; WebPageData *preview = nullptr;
QString link; QString link;
@ -619,41 +711,90 @@ void DraftOptionsBox(
Fn<void(const QString &link, WebPageData *page)> performSwitch; Fn<void(const QString &link, WebPageData *page)> performSwitch;
Fn<void(const QString &link, bool force)> requestAndSwitch; Fn<void(const QString &link, bool force)> requestAndSwitch;
rpl::lifetime resolveLifetime; rpl::lifetime resolveLifetime;
Fn<void()> rebuild;
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->link = args.usedLink;
state->quote = SelectedQuote{ state->quote = SelectedQuote{
replyItem, replyItem,
draft.reply.quote, draft.reply.quote,
draft.reply.quoteOffset, draft.reply.quoteOffset,
}; };
state->forward = std::move(args.forward);
state->webpage = draft.webpage; state->webpage = draft.webpage;
state->preview = previewData; state->preview = previewData;
state->shown = previewData ? Section::Link : Section::Reply;
if (replyItem && previewData) { state->rebuild = [=] {
box->setNoContentMargin(true); const auto hasLink = (state->preview != nullptr);
state->tabs = box->setPinnedToTopContent( const auto hasReply = (state->quote.current().item != nullptr);
object_ptr<Ui::SettingsSlider>( const auto hasForward = !state->forward.items.empty();
box.get(), if (!hasLink && !hasReply && !hasForward) {
st::defaultTabsSlider)); box->closeBox();
state->tabs->resizeToWidth(st::boxWideWidth); return;
state->tabs->move(0, 0); }
state->tabs->setRippleTopRoundRadius(st::boxRadius); const auto section = state->shown.current();
state->tabs->setSections({ const auto changeSection = (section == Section::Link)
tr::lng_reply_header_short(tr::now), ? !hasLink
tr::lng_link_header_short(tr::now), : (section == Section::Reply)
}); ? !hasReply
state->tabs->setActiveSectionFast(1); : !hasForward;
state->tabs->sectionActivated( const auto now = !changeSection
) | rpl::start_with_next([=](int section) { ? section
state->shown = section ? Section::Link : Section::Reply; : hasLink
}, box->lifetime()); ? Section::Link
} else { : hasReply
box->setTitle(previewData ? Section::Reply
? tr::lng_link_options_header() : Section::Forward;
: draft.reply.quote.empty() auto labels = std::vector<QString>();
? tr::lng_reply_options_header() auto indices = base::flat_map<Section, int>();
: tr::lng_reply_options_quote()); auto sections = std::vector<Section>();
} const auto push = [&](Section section, tr::phrase<> phrase) {
indices[section] = labels.size();
labels.push_back(phrase(tr::now));
sections.push_back(section);
};
if (hasLink) {
push(Section::Link, tr::lng_link_header_short);
}
if (hasReply) {
push(Section::Reply, tr::lng_reply_header_short);
}
if (hasForward) {
push(Section::Forward, tr::lng_forward_header_short);
}
if (labels.size() > 1) {
box->setNoContentMargin(true);
state->tabs = box->setPinnedToTopContent(
object_ptr<Ui::SettingsSlider>(
box.get(),
st::defaultTabsSlider));
state->tabs->resizeToWidth(st::boxWideWidth);
state->tabs->move(0, 0);
state->tabs->setRippleTopRoundRadius(st::boxRadius);
state->tabs->setSections(labels);
state->tabs->setActiveSectionFast(indices[now]);
state->tabs->sectionActivated(
) | rpl::start_with_next([=](int index) {
state->shown = sections[index];
}, box->lifetime());
} else {
const auto forwardCount = state->forward.items.size();
box->setTitle(hasLink
? tr::lng_link_options_header()
: hasReply
? (state->quote.current().text.empty()
? tr::lng_reply_options_header()
: tr::lng_reply_options_quote())
: (forwardCount == 1)
? tr::lng_forward_title()
: tr::lng_forward_many_title(
lt_count,
rpl::single(forwardCount * 1.0)));
}
state->shown.force_assign(now);
};
state->rebuild();
const auto bottom = box->setPinnedToBottomContent( const auto bottom = box->setPinnedToBottomContent(
object_ptr<Ui::VerticalLayout>(box)); object_ptr<Ui::VerticalLayout>(box));
@ -675,9 +816,17 @@ void DraftOptionsBox(
}; };
const auto finish = [=]( const auto finish = [=](
FullReplyTo result, FullReplyTo result,
Data::WebPageDraft webpage) { Data::WebPageDraft webpage,
std::optional<Data::ForwardOptions> options) {
const auto weak = Ui::MakeWeak(box); const auto weak = Ui::MakeWeak(box);
done(std::move(result), std::move(webpage)); auto forward = Data::ForwardDraft();
if (options) {
forward.options = *options;
for (const auto &item : state->forward.items) {
forward.ids.push_back(item->fullId());
}
}
done(std::move(result), std::move(webpage), std::move(forward));
if (const auto strong = weak.data()) { if (const auto strong = weak.data()) {
strong->closeBox(); strong->closeBox();
} }
@ -716,7 +865,7 @@ void DraftOptionsBox(
st::settingsAttentionButtonWithIcon, st::settingsAttentionButtonWithIcon,
{ &st::menuIconDeleteAttention } { &st::menuIconDeleteAttention }
)->setClickedCallback([=] { )->setClickedCallback([=] {
finish({}, state->webpage); finish({}, state->webpage, state->forward.options);
}); });
if (!item->originalText().empty()) { if (!item->originalText().empty()) {
@ -774,7 +923,8 @@ void DraftOptionsBox(
st::settingsAttentionButtonWithIcon, st::settingsAttentionButtonWithIcon,
{ &st::menuIconDeleteAttention } { &st::menuIconDeleteAttention }
)->setClickedCallback([=] { )->setClickedCallback([=] {
finish(resolveReply(), { .removed = true }); const auto options = state->forward.options;
finish(resolveReply(), { .removed = true }, options);
}); });
if (args.links.size() > 1) { if (args.links.size() > 1) {
@ -783,6 +933,92 @@ void DraftOptionsBox(
} }
}; };
const auto setupForwardActions = [=] {
using Options = Data::ForwardOptions;
const auto now = state->forward.options;
const auto &items = state->forward.items;
const auto count = items.size();
const auto dropNames = (now != Options::PreserveInfo);
const auto sendersCount = ItemsForwardSendersCount(items);
const auto captionsCount = ItemsForwardCaptionsCount(items);
const auto hasOnlyForcedForwardedInfo = !captionsCount
&& HasOnlyForcedForwardedInfo(items);
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
AddFilledSkip(bottom);
if (!hasOnlyForcedForwardedInfo) {
Settings::AddButtonWithIcon(
bottom,
(dropNames
? (sendersCount == 1
? tr::lng_forward_action_show_sender
: tr::lng_forward_action_show_senders)
: (sendersCount == 1
? tr::lng_forward_action_hide_sender
: tr::lng_forward_action_hide_senders))(),
st::settingsButton,
{ dropNames
? &st::menuIconUserShow
: &st::menuIconUserHide }
)->setClickedCallback([=] {
state->forward.options = dropNames
? Options::PreserveInfo
: Options::NoSenderNames;
state->shown.force_assign(Section::Forward);
});
}
if (captionsCount) {
Settings::AddButtonWithIcon(
bottom,
(dropCaptions
? (captionsCount == 1
? tr::lng_forward_action_show_caption
: tr::lng_forward_action_show_captions)
: (captionsCount == 1
? tr::lng_forward_action_hide_caption
: tr::lng_forward_action_hide_captions))(),
st::settingsButton,
{ dropCaptions
? &st::menuIconCaptionShow
: &st::menuIconCaptionHide }
)->setClickedCallback([=] {
state->forward.options = dropCaptions
? Options::NoSenderNames
: Options::NoNamesAndCaptions;
state->shown.force_assign(Section::Forward);
});
}
Settings::AddButtonWithIcon(
bottom,
tr::lng_forward_action_change_recipient(),
st::settingsButton,
{ &st::menuIconReplace }
)->setClickedCallback([=] {
auto draft = base::take(state->forward);
finish(resolveReply(), state->webpage, std::nullopt);
Window::ShowForwardMessagesBox(show, {
.ids = show->session().data().itemsToIds(draft.items),
.options = draft.options,
});
});
Settings::AddButtonWithIcon(
bottom,
tr::lng_forward_action_remove(),
st::settingsAttentionButtonWithIcon,
{ &st::menuIconDeleteAttention }
)->setClickedCallback([=] {
finish(resolveReply(), state->webpage, std::nullopt);
});
AddFilledSkip(bottom);
Ui::AddDividerText(bottom, (count == 1
? tr::lng_forward_about()
: tr::lng_forward_many_about()));
};
const auto &resolver = args.resolver; const auto &resolver = args.resolver;
state->performSwitch = [=](const QString &link, WebPageData *page) { state->performSwitch = [=](const QString &link, WebPageData *page) {
const auto now = base::unixtime::now(); const auto now = base::unixtime::now();
@ -847,20 +1083,27 @@ void DraftOptionsBox(
state->shown.value() | rpl::start_with_next([=](Section shown) { state->shown.value() | rpl::start_with_next([=](Section shown) {
bottom->clear(); bottom->clear();
state->shownLifetime.destroy(); state->shownLifetime.destroy();
if (shown == Section::Reply) { switch (shown) {
state->quote = state->wrap->showQuoteSelector( case Section::Reply: {
state->quote.current()); state->quote = state->wrap->showQuoteSelector(
setupReplyActions(); state->quote.current());
} else { setupReplyActions();
state->wrap->showLinkSelector( } break;
draft.textWithTags, case Section::Link: {
state->webpage, state->wrap->showLinkSelector(
linkRanges, draft.textWithTags,
state->link state->webpage,
) | rpl::start_with_next([=](QString link) { linkRanges,
switchTo(link); state->link
}, state->shownLifetime); ) | rpl::start_with_next([=](QString link) {
setupLinkActions(); switchTo(link);
}, state->shownLifetime);
setupLinkActions();
} break;
case Section::Forward: {
state->wrap->showForwardSelector(state->forward);
setupForwardActions();
} break;
} }
}, box->lifetime()); }, box->lifetime());
@ -879,7 +1122,8 @@ void DraftOptionsBox(
.text = { tr::lng_reply_quote_long_text(tr::now) }, .text = { tr::lng_reply_quote_long_text(tr::now) },
}); });
} else { } else {
finish(resolveReply(), state->webpage); const auto options = state->forward.options;
finish(resolveReply(), state->webpage, options);
} }
}); });
@ -887,31 +1131,29 @@ void DraftOptionsBox(
box->closeBox(); box->closeBox();
}); });
if (replyItem) { args.show->session().data().itemRemoved(
args.show->session().data().itemRemoved( ) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
) | rpl::filter([=](not_null<const HistoryItem*> removed) { const auto inReply = (state->quote.current().item == removed);
const auto current = state->quote.current().item; if (inReply) {
if ((removed == replyItem) || (removed == current)) { state->quote = SelectedQuote();
return true; }
} const auto i = ranges::find(state->forward.items, removed);
const auto group = current->history()->owner().groups().find( const auto inForward = (i != end(state->forward.items));
current); if (inForward) {
return (group && ranges::contains(group->items, removed)); state->forward.items.erase(i);
}) | rpl::start_with_next([=] { }
if (previewData) { if (inReply || inForward) {
state->tabs = nullptr; state->rebuild();
box->setPinnedToTopContent( }
object_ptr<Ui::RpWidget>(nullptr)); }, box->lifetime());
box->setNoContentMargin(false);
box->setTitle(state->quote.current().text.empty() args.show->session().data().itemViewRefreshRequest(
? tr::lng_reply_options_header() ) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
: tr::lng_reply_options_quote()); if (state->wrap->hasViewForItem(item)) {
state->shown = Section::Link; state->rebuild();
} else { }
box->closeBox(); }, box->lifetime());
}
}, box->lifetime());
}
} }
struct AuthorSelector { struct AuthorSelector {
@ -1165,7 +1407,7 @@ void EditDraftOptions(EditDraftOptionsArgs &&args) {
&& !previewDataRaw->failed) && !previewDataRaw->failed)
? previewDataRaw ? previewDataRaw
: nullptr; : nullptr;
if (!replyItem && !previewData) { if (!replyItem && !previewData && args.forward.items.empty()) {
return; return;
} }
args.show->show( args.show->show(

View file

@ -29,9 +29,10 @@ struct EditDraftOptionsArgs {
not_null<History*> history; not_null<History*> history;
Data::Draft draft; Data::Draft draft;
QString usedLink; QString usedLink;
Data::ResolvedForwardDraft forward;
std::vector<MessageLinkRange> links; std::vector<MessageLinkRange> links;
std::shared_ptr<WebpageResolver> resolver; std::shared_ptr<WebpageResolver> resolver;
Fn<void(FullReplyTo, Data::WebPageDraft)> done; Fn<void(FullReplyTo, Data::WebPageDraft, Data::ForwardDraft)> done;
Fn<void(FullReplyTo)> highlight; Fn<void(FullReplyTo)> highlight;
Fn<void()> clearOldDraft; Fn<void()> clearOldDraft;
}; };

View file

@ -44,19 +44,6 @@ constexpr auto kUnknownVersion = -1;
constexpr auto kNameWithCaptionsVersion = -2; constexpr auto kNameWithCaptionsVersion = -2;
constexpr auto kNameNoCaptionsVersion = -3; constexpr auto kNameNoCaptionsVersion = -3;
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
for (const auto &item : list) {
if (const auto media = item->media()) {
if (!media->forceForwardedInfo()) {
return false;
}
} else {
return false;
}
}
return true;
}
} // namespace } // namespace
ForwardPanel::ForwardPanel(Fn<void()> repaint) ForwardPanel::ForwardPanel(Fn<void()> repaint)
@ -228,6 +215,10 @@ void ForwardPanel::itemRemoved(not_null<const HistoryItem*> item) {
} }
} }
const Data::ResolvedForwardDraft &ForwardPanel::draft() const {
return _data;
}
const HistoryItemsList &ForwardPanel::items() const { const HistoryItemsList &ForwardPanel::items() const {
return _data.items; return _data.items;
} }
@ -236,63 +227,17 @@ bool ForwardPanel::empty() const {
return _data.items.empty(); return _data.items.empty();
} }
void ForwardPanel::editOptions(std::shared_ptr<ChatHelpers::Show> show) { void ForwardPanel::applyOptions(Data::ForwardOptions options) {
using Options = Data::ForwardOptions; if (_data.items.empty()) {
const auto now = _data.options;
const auto count = _data.items.size();
const auto dropNames = (now != Options::PreserveInfo);
const auto sendersCount = ItemsForwardSendersCount(_data.items);
const auto captionsCount = ItemsForwardCaptionsCount(_data.items);
const auto hasOnlyForcedForwardedInfo = !captionsCount
&& HasOnlyForcedForwardedInfo(_data.items);
const auto dropCaptions = (now == Options::NoNamesAndCaptions);
const auto weak = base::make_weak(this);
const auto changeRecipient = crl::guard(this, [=] {
if (_data.items.empty()) {
return;
}
auto data = base::take(_data);
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {});
Window::ShowForwardMessagesBox(show, {
.ids = _to->owner().itemsToIds(data.items),
.options = data.options,
});
});
if (hasOnlyForcedForwardedInfo) {
changeRecipient();
return; return;
} else if (_data.options != options) {
_data.options = options;
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {
.ids = _to->owner().itemsToIds(_data.items),
.options = options,
});
_repaint();
} }
const auto optionsChanged = crl::guard(weak, [=](
Ui::ForwardOptions options) {
if (_data.items.empty()) {
return;
}
const auto newOptions = (options.captionsCount
&& options.dropCaptions)
? Options::NoNamesAndCaptions
: options.dropNames
? Options::NoSenderNames
: Options::PreserveInfo;
if (_data.options != newOptions) {
_data.options = newOptions;
_to->owningHistory()->setForwardDraft(_to->topicRootId(), {
.ids = _to->owner().itemsToIds(_data.items),
.options = newOptions,
});
_repaint();
}
});
show->showBox(Box(
Ui::ForwardOptionsBox,
count,
Ui::ForwardOptions{
.sendersCount = sendersCount,
.captionsCount = captionsCount,
.dropNames = dropNames,
.dropCaptions = dropCaptions,
},
optionsChanged,
changeRecipient));
} }
void ForwardPanel::editToNextOption() { void ForwardPanel::editToNextOption() {
@ -485,7 +430,19 @@ void EditWebPageOptions(
box->closeBox(); box->closeBox();
}); });
})); }));
}
bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list) {
for (const auto &item : list) {
if (const auto media = item->media()) {
if (!media->forceForwardedInfo()) {
return false;
}
} else {
return false;
}
}
return true;
} }
} // namespace HistoryView::Controls } // namespace HistoryView::Controls

View file

@ -47,9 +47,10 @@ public:
[[nodiscard]] rpl::producer<> itemsUpdated() const; [[nodiscard]] rpl::producer<> itemsUpdated() const;
void editOptions(std::shared_ptr<ChatHelpers::Show> show); void applyOptions(Data::ForwardOptions options);
void editToNextOption(); void editToNextOption();
[[nodiscard]] const Data::ResolvedForwardDraft &draft() const;
[[nodiscard]] const HistoryItemsList &items() const; [[nodiscard]] const HistoryItemsList &items() const;
[[nodiscard]] bool empty() const; [[nodiscard]] bool empty() const;
@ -83,4 +84,6 @@ void EditWebPageOptions(
Data::WebPageDraft draft, Data::WebPageDraft draft,
Fn<void(Data::WebPageDraft)> done); Fn<void(Data::WebPageDraft)> done);
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list);
} // namespace HistoryView::Controls } // namespace HistoryView::Controls

View file

@ -65,72 +65,4 @@ void FillForwardOptions(
} }
} }
void ForwardOptionsBox(
not_null<GenericBox*> box,
int count,
ForwardOptions options,
Fn<void(ForwardOptions)> optionsChanged,
Fn<void()> changeRecipient) {
Expects(optionsChanged != nullptr);
Expects(changeRecipient != nullptr);
box->setTitle((count == 1)
? tr::lng_forward_title()
: tr::lng_forward_many_title(
lt_count,
rpl::single(count) | tr::to_count()));
box->addButton(tr::lng_box_done(), [=] {
box->closeBox();
});
box->events(
) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (e->type() == QEvent::KeyPress) {
const auto k = static_cast<QKeyEvent*>(e.get());
if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) {
box->closeBox();
}
}
}, box->lifetime());
box->addRow(
object_ptr<Ui::FlatLabel>(
box.get(),
(count == 1
? tr::lng_forward_about()
: tr::lng_forward_many_about()),
st::boxLabel),
st::boxRowPadding);
const auto checkboxPadding = style::margins(
st::boxRowPadding.left(),
st::boxRowPadding.left(),
st::boxRowPadding.right(),
st::boxRowPadding.bottom());
auto createView = [&](rpl::producer<QString> &&text, bool checked) {
return box->addRow(
object_ptr<Ui::Checkbox>(
box.get(),
std::move(text),
checked,
st::defaultBoxCheckbox),
checkboxPadding)->checkView();
};
FillForwardOptions(
std::move(createView),
options,
std::move(optionsChanged),
box->lifetime());
box->addRow(
object_ptr<Ui::LinkButton>(
box.get(),
tr::lng_forward_change_recipient(tr::now)),
checkboxPadding
)->setClickedCallback([=] {
box->closeBox();
changeRecipient();
});
}
} // namespace Ui } // namespace Ui

View file

@ -28,11 +28,4 @@ void FillForwardOptions(
Fn<void(ForwardOptions)> optionsChanged, Fn<void(ForwardOptions)> optionsChanged,
rpl::lifetime &lifetime); rpl::lifetime &lifetime);
void ForwardOptionsBox(
not_null<GenericBox*> box,
int count,
ForwardOptions options,
Fn<void(ForwardOptions)> optionsChanged,
Fn<void()> changeRecipient);
} // namespace Ui } // namespace Ui

View file

@ -150,6 +150,10 @@ menuIconAbove: icon {{ "menu/link_above", menuIconColor }};
menuIconBelow: icon {{ "menu/link_below", menuIconColor }}; menuIconBelow: icon {{ "menu/link_below", menuIconColor }};
menuIconEnlarge: icon {{ "menu/link_enlarge", menuIconColor }}; menuIconEnlarge: icon {{ "menu/link_enlarge", menuIconColor }};
menuIconShrink: icon {{ "menu/link_shrink", menuIconColor }}; menuIconShrink: icon {{ "menu/link_shrink", menuIconColor }};
menuIconUserShow: icon {{ "menu/name_show", menuIconColor }};
menuIconUserHide: icon {{ "menu/name_hide", menuIconColor }};
menuIconCaptionShow: icon {{ "menu/caption_show", menuIconColor }};
menuIconCaptionHide: icon {{ "menu/caption_hide", menuIconColor }};
menuIconAsTopics: icon {{ "menu/mode_topics", menuIconColor }}; menuIconAsTopics: icon {{ "menu/mode_topics", menuIconColor }};
menuIconAsMessages: icon {{ "menu/mode_messages", menuIconColor }}; menuIconAsMessages: icon {{ "menu/mode_messages", menuIconColor }};
menuIconTagFilter: icon{{ "menu/tag_filter", menuIconColor }}; menuIconTagFilter: icon{{ "menu/tag_filter", menuIconColor }};