Allow forward+reply, options in single box.
BIN
Telegram/Resources/icons/menu/caption_hide.png
Normal file
After Width: | Height: | Size: 718 B |
BIN
Telegram/Resources/icons/menu/caption_hide@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
Telegram/Resources/icons/menu/caption_hide@3x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
Telegram/Resources/icons/menu/caption_show.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
Telegram/Resources/icons/menu/caption_show@2x.png
Normal file
After Width: | Height: | Size: 763 B |
BIN
Telegram/Resources/icons/menu/caption_show@3x.png
Normal file
After Width: | Height: | Size: 963 B |
BIN
Telegram/Resources/icons/menu/name_hide.png
Normal file
After Width: | Height: | Size: 615 B |
BIN
Telegram/Resources/icons/menu/name_hide@2x.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Telegram/Resources/icons/menu/name_hide@3x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/menu/name_show.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
Telegram/Resources/icons/menu/name_show@2x.png
Normal file
After Width: | Height: | Size: 959 B |
BIN
Telegram/Resources/icons/menu/name_show@3x.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
|
@ -4912,6 +4912,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_forward_show_captions" = "Show captions";
|
||||
"lng_forward_change_recipient" = "Change recipient";
|
||||
"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_request1" = "{bot} requests access to your personal data";
|
||||
|
|
|
@ -36,6 +36,8 @@ using Options = base::flags<Option>;
|
|||
|
||||
namespace Data {
|
||||
|
||||
struct FileOrigin;
|
||||
|
||||
struct UploadState {
|
||||
explicit UploadState(int64 size) : size(size) {
|
||||
}
|
||||
|
@ -58,8 +60,6 @@ constexpr auto kVoiceMessageCacheTag = uint8(0x03);
|
|||
constexpr auto kVideoMessageCacheTag = uint8(0x04);
|
||||
constexpr auto kAnimationCacheTag = uint8(0x05);
|
||||
|
||||
struct FileOrigin;
|
||||
|
||||
} // namespace Data
|
||||
|
||||
struct MessageGroupId {
|
||||
|
@ -342,3 +342,29 @@ enum class MediaWebPageFlag : uint8 {
|
|||
};
|
||||
inline constexpr bool is_flag_type(MediaWebPageFlag) { return true; }
|
||||
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
|
||||
|
|
|
@ -26,7 +26,6 @@ class HistoryMainElementDelegateMixin;
|
|||
struct LanguageId;
|
||||
|
||||
namespace Data {
|
||||
|
||||
struct Draft;
|
||||
class Session;
|
||||
class Folder;
|
||||
|
@ -34,29 +33,6 @@ class ChatFilter;
|
|||
struct SponsoredFrom;
|
||||
class SponsoredMessages;
|
||||
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 Dialogs {
|
||||
|
|
|
@ -136,6 +136,9 @@ template <typename T>
|
|||
HistoryItemCommonFields fields,
|
||||
not_null<History*> history,
|
||||
not_null<HistoryItem*> original) {
|
||||
if (fields.flags & MessageFlag::FakeHistoryItem) {
|
||||
return fields;
|
||||
}
|
||||
fields.flags |= NewForwardedFlags(history->peer, fields.from, original);
|
||||
return fields;
|
||||
}
|
||||
|
@ -509,7 +512,8 @@ HistoryItem::HistoryItem(
|
|||
auto config = CreateConfig();
|
||||
|
||||
const auto originalMedia = original->media();
|
||||
const auto dropForwardInfo = original->computeDropForwardedInfo();
|
||||
const auto dropForwardInfo = fields.ignoreForwardFrom
|
||||
|| original->computeDropForwardedInfo();
|
||||
const auto topicRootId = fields.replyTo.topicRootId;
|
||||
config.reply.messageId = config.reply.topMessageId = topicRootId;
|
||||
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())
|
||||
: original->originalText());
|
||||
|
||||
if (fields.groupedId) {
|
||||
setGroupId(MessageGroupId::FromRaw(
|
||||
history->peer->id,
|
||||
fields.groupedId,
|
||||
_flags & MessageFlag::IsOrWasScheduled));
|
||||
}
|
||||
}
|
||||
|
||||
HistoryItem::HistoryItem(
|
||||
|
|
|
@ -107,6 +107,8 @@ struct HistoryItemCommonFields {
|
|||
uint64 groupedId = 0;
|
||||
EffectId effectId = 0;
|
||||
HistoryMessageMarkupData markup;
|
||||
bool ignoreForwardFrom = false;
|
||||
bool ignoreForwardCaptions = false;
|
||||
};
|
||||
|
||||
enum class HistoryReactionSource : char {
|
||||
|
|
|
@ -2109,7 +2109,7 @@ bool HistoryWidget::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
|||
if (!_replyEditMsg) {
|
||||
requestMessageData(_editMsgId);
|
||||
}
|
||||
} else if (!readyToForward()) {
|
||||
} else {
|
||||
const auto draft = _history->localDraft({});
|
||||
_processingReplyTo = draft ? draft->reply : FullReplyTo();
|
||||
if (_processingReplyTo) {
|
||||
|
@ -4982,7 +4982,7 @@ bool HistoryWidget::showRecordButton() const {
|
|||
&& !_voiceRecordBar->isRecordingByAnotherBar()
|
||||
&& !HasSendText(_field)
|
||||
&& !_previewDrawPreview
|
||||
&& !readyToForward()
|
||||
&& (_replyTo || !readyToForward())
|
||||
&& !_editMsgId;
|
||||
}
|
||||
|
||||
|
@ -6825,18 +6825,15 @@ void HistoryWidget::mousePressEvent(QMouseEvent *e) {
|
|||
_peer,
|
||||
Window::SectionShow::Way::Forward,
|
||||
_editMsgId);
|
||||
} else if (isReadyToForward) {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
_forwardPanel->editToNextOption();
|
||||
} else {
|
||||
_forwardPanel->editOptions(controller()->uiShow());
|
||||
}
|
||||
} else if (_replyTo
|
||||
&& ((e->modifiers() & Qt::ControlModifier)
|
||||
|| (e->button() != Qt::LeftButton))) {
|
||||
jumpToReply(_replyTo);
|
||||
} else if (_replyTo) {
|
||||
} else if (_replyTo
|
||||
|| (isReadyToForward && e->button() == Qt::LeftButton)) {
|
||||
editDraftOptions();
|
||||
} else if (isReadyToForward) {
|
||||
_forwardPanel->editToNextOption();
|
||||
} else if (_kbReplyTo) {
|
||||
controller()->showPeerHistory(
|
||||
_kbReplyTo->history()->peer->id,
|
||||
|
@ -6851,15 +6848,18 @@ void HistoryWidget::editDraftOptions() {
|
|||
const auto history = _history;
|
||||
const auto reply = _replyTo;
|
||||
const auto webpage = _preview->draft();
|
||||
const auto forward = _forwardPanel->draft();
|
||||
|
||||
const auto done = [=](
|
||||
FullReplyTo replyTo,
|
||||
Data::WebPageDraft webpage) {
|
||||
Data::WebPageDraft webpage,
|
||||
Data::ForwardDraft forward) {
|
||||
if (replyTo) {
|
||||
replyToMessage(replyTo);
|
||||
} else {
|
||||
cancelReply();
|
||||
}
|
||||
history->setForwardDraft({}, std::move(forward));
|
||||
_preview->apply(webpage);
|
||||
};
|
||||
const auto replyToId = reply.messageId;
|
||||
|
@ -6873,6 +6873,7 @@ void HistoryWidget::editDraftOptions() {
|
|||
.history = history,
|
||||
.draft = Data::Draft(_field, reply, _preview->draft()),
|
||||
.usedLink = _preview->link(),
|
||||
.forward = _forwardPanel->draft(),
|
||||
.links = _preview->links(),
|
||||
.resolver = _preview->resolver(),
|
||||
.done = done,
|
||||
|
@ -7956,7 +7957,7 @@ void HistoryWidget::processReply() {
|
|||
{ .ids = { 1, itemId } });
|
||||
}),
|
||||
.confirmText = tr::lng_selected_forward(),
|
||||
}));
|
||||
}));
|
||||
}
|
||||
return processCancel();
|
||||
#endif
|
||||
|
@ -7985,7 +7986,6 @@ void HistoryWidget::setReplyFieldsFromProcessing() {
|
|||
return;
|
||||
}
|
||||
|
||||
_history->setForwardDraft(MsgId(), {});
|
||||
if (_composeSearch) {
|
||||
_composeSearch->hideAnimated();
|
||||
}
|
||||
|
@ -8243,10 +8243,10 @@ void HistoryWidget::cancelFieldAreaState() {
|
|||
_preview->apply({ .removed = true });
|
||||
} else if (_editMsgId) {
|
||||
cancelEdit();
|
||||
} else if (readyToForward()) {
|
||||
_history->setForwardDraft(MsgId(), {});
|
||||
} else if (_replyTo) {
|
||||
cancelReply();
|
||||
} else if (readyToForward()) {
|
||||
_history->setForwardDraft(MsgId(), {});
|
||||
} else if (_kbReplyTo) {
|
||||
toggleKeyboard();
|
||||
}
|
||||
|
@ -8595,9 +8595,6 @@ void HistoryWidget::updateForwarding() {
|
|||
_forwardPanel->update(_history, _history
|
||||
? _history->resolveForwardDraft(MsgId())
|
||||
: Data::ResolvedForwardDraft());
|
||||
if (readyToForward()) {
|
||||
cancelReply();
|
||||
}
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
}
|
||||
|
|
|
@ -146,6 +146,7 @@ public:
|
|||
[[nodiscard]] bool isEditingMessage() const;
|
||||
[[nodiscard]] bool readyToForward() const;
|
||||
[[nodiscard]] const HistoryItemsList &forwardItems() const;
|
||||
[[nodiscard]] const Data::ResolvedForwardDraft &forwardDraft() const;
|
||||
[[nodiscard]] FullReplyTo replyingToMessage() const;
|
||||
[[nodiscard]] FullMsgId editMsgId() const;
|
||||
[[nodiscard]] rpl::producer<FullMsgId> editMsgIdValue() const;
|
||||
|
@ -281,23 +282,23 @@ void FieldHeader::init() {
|
|||
st::historyLinkIcon.paint(p, position, width());
|
||||
} else if (isEditingMessage()) {
|
||||
st::historyEditIcon.paint(p, position, width());
|
||||
} else if (readyToForward()) {
|
||||
st::historyForwardIcon.paint(p, position, width());
|
||||
} else if (const auto reply = replyingToMessage()) {
|
||||
if (!reply.quote.empty()) {
|
||||
st::historyQuoteIcon.paint(p, position, width());
|
||||
} else {
|
||||
st::historyReplyIcon.paint(p, position, width());
|
||||
}
|
||||
} else if (readyToForward()) {
|
||||
st::historyForwardIcon.paint(p, position, width());
|
||||
}
|
||||
|
||||
if (_preview.parsed) {
|
||||
paintWebPage(
|
||||
p,
|
||||
_history ? _history->peer : _data->session().user());
|
||||
} else if (isEditingMessage() || !readyToForward()) {
|
||||
} else if (isEditingMessage() || replyingToMessage()) {
|
||||
paintEditOrReplyToMessage(p);
|
||||
} else {
|
||||
} else if (readyToForward()) {
|
||||
paintForwardInfo(p);
|
||||
}
|
||||
}, lifetime());
|
||||
|
@ -339,10 +340,10 @@ void FieldHeader::init() {
|
|||
_previewCancelled.fire({});
|
||||
} else if (_editMsgId.current()) {
|
||||
_editCancelled.fire({});
|
||||
} else if (readyToForward()) {
|
||||
_forwardCancelled.fire({});
|
||||
} else if (_replyTo.current()) {
|
||||
_replyCancelled.fire({});
|
||||
} else if (readyToForward()) {
|
||||
_forwardCancelled.fire({});
|
||||
}
|
||||
updateVisible();
|
||||
update();
|
||||
|
@ -403,12 +404,9 @@ void FieldHeader::init() {
|
|||
_jumpToItemRequests.fire(FullReplyTo{
|
||||
.messageId = _editMsgId.current()
|
||||
});
|
||||
} else if (readyToForward()) {
|
||||
_forwardPanel->editOptions(_show);
|
||||
} else if (reply
|
||||
&& (e->modifiers() & Qt::ControlModifier)) {
|
||||
} else if (reply && (e->modifiers() & Qt::ControlModifier)) {
|
||||
_jumpToItemRequests.fire_copy(reply);
|
||||
} else if (reply) {
|
||||
} else if (reply || readyToForward()) {
|
||||
_editOptionsRequests.fire({});
|
||||
}
|
||||
} else if (!isLeftButton) {
|
||||
|
@ -419,6 +417,8 @@ void FieldHeader::init() {
|
|||
_hasSendText());
|
||||
} else if (const auto reply = replyingToMessage()) {
|
||||
_jumpToItemRequests.fire_copy(reply);
|
||||
} else if (readyToForward()) {
|
||||
_forwardPanel->editToNextOption();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -713,6 +713,10 @@ const HistoryItemsList &FieldHeader::forwardItems() const {
|
|||
return _forwardPanel->items();
|
||||
}
|
||||
|
||||
const Data::ResolvedForwardDraft &FieldHeader::forwardDraft() const {
|
||||
return _forwardPanel->draft();
|
||||
}
|
||||
|
||||
FullReplyTo FieldHeader::replyingToMessage() const {
|
||||
return _replyTo.current();
|
||||
}
|
||||
|
@ -764,9 +768,6 @@ void FieldHeader::updateForwarding(
|
|||
Data::Thread *thread,
|
||||
Data::ResolvedForwardDraft items) {
|
||||
_forwardPanel->update(thread, std::move(items));
|
||||
if (readyToForward()) {
|
||||
replyToMessage({});
|
||||
}
|
||||
updateControlsGeometry(size());
|
||||
}
|
||||
|
||||
|
@ -1408,12 +1409,14 @@ void ComposeControls::init() {
|
|||
|
||||
const auto done = [=](
|
||||
FullReplyTo replyTo,
|
||||
Data::WebPageDraft webpage) {
|
||||
Data::WebPageDraft webpage,
|
||||
Data::ForwardDraft forward) {
|
||||
if (replyTo) {
|
||||
replyToMessage(replyTo);
|
||||
} else {
|
||||
cancelReplyMessage();
|
||||
}
|
||||
history->setForwardDraft(topicRootId, std::move(forward));
|
||||
_preview->apply(webpage);
|
||||
_field->setFocus();
|
||||
};
|
||||
|
@ -1428,6 +1431,7 @@ void ComposeControls::init() {
|
|||
.history = history,
|
||||
.draft = Data::Draft(_field, reply, _preview->draft()),
|
||||
.usedLink = _preview->link(),
|
||||
.forward = _header->forwardDraft(),
|
||||
.links = _preview->links(),
|
||||
.resolver = _preview->resolver(),
|
||||
.done = done,
|
||||
|
@ -2013,9 +2017,6 @@ void ComposeControls::applyDraft(FieldHistoryAction fieldHistoryAction) {
|
|||
_canReplaceMedia = _canAddMedia = false;
|
||||
_photoEditMedia = nullptr;
|
||||
_header->replyToMessage(draft->reply);
|
||||
if (_header->replyingToMessage()) {
|
||||
cancelForward();
|
||||
}
|
||||
_header->editMessage({});
|
||||
if (_preview) {
|
||||
_preview->setDisabled(false);
|
||||
|
@ -3018,9 +3019,6 @@ void ComposeControls::replyToMessage(FullReplyTo id) {
|
|||
}
|
||||
} else {
|
||||
_header->replyToMessage(id);
|
||||
if (_header->replyingToMessage()) {
|
||||
cancelForward();
|
||||
}
|
||||
}
|
||||
|
||||
_saveDraftText = true;
|
||||
|
@ -3071,12 +3069,12 @@ bool ComposeControls::handleCancelRequest() {
|
|||
} else if (isEditingMessage()) {
|
||||
maybeCancelEditMessage();
|
||||
return true;
|
||||
} else if (readyToForward()) {
|
||||
cancelForward();
|
||||
return true;
|
||||
} else if (replyingToMessage()) {
|
||||
cancelReplyMessage();
|
||||
return true;
|
||||
} else if (readyToForward()) {
|
||||
cancelForward();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#include "history/view/controls/history_view_draft_options.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/timer_rpl.h"
|
||||
#include "base/unixtime.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_user.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/history_view_element.h"
|
||||
#include "history/view/history_view_cursor_state.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "history/history_item_components.h"
|
||||
#include "history/history_item_helpers.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "main/main_session.h"
|
||||
#include "settings/settings_common.h"
|
||||
|
@ -41,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/ui_utility.h"
|
||||
#include "window/themes/window_theme.h"
|
||||
#include "window/section_widget.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
@ -56,6 +60,7 @@ namespace {
|
|||
|
||||
enum class Section {
|
||||
Reply,
|
||||
Forward,
|
||||
Link,
|
||||
};
|
||||
|
||||
|
@ -104,6 +109,10 @@ public:
|
|||
not_null<History*> history);
|
||||
~PreviewWrap();
|
||||
|
||||
[[nodiscard]] bool hasViewForItem(
|
||||
not_null<const HistoryItem*> item) const;
|
||||
|
||||
void showForwardSelector(Data::ResolvedForwardDraft draft);
|
||||
[[nodiscard]] rpl::producer<SelectedQuote> showQuoteSelector(
|
||||
const SelectedQuote "e);
|
||||
[[nodiscard]] rpl::producer<QString> showLinkSelector(
|
||||
|
@ -117,6 +126,11 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
HistoryItem *item = nullptr;
|
||||
std::unique_ptr<Element> view;
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void leaveEventHook(QEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
@ -129,7 +143,8 @@ private:
|
|||
_visibleBottom = bottom;
|
||||
}
|
||||
|
||||
void initElement();
|
||||
void clear(std::vector<Entry> entries);
|
||||
void initElements();
|
||||
void highlightUsedLink(
|
||||
const TextWithTags &message,
|
||||
const QString &usedLink,
|
||||
|
@ -144,8 +159,8 @@ private:
|
|||
const std::unique_ptr<PreviewDelegate> _delegate;
|
||||
|
||||
Section _section = Section::Reply;
|
||||
HistoryItem *_draftItem = nullptr;
|
||||
std::unique_ptr<Element> _element;
|
||||
std::vector<Entry> _entries;
|
||||
base::flat_set<not_null<const Element*>> _views;
|
||||
rpl::variable<TextSelection> _selection;
|
||||
rpl::event_stream<QString> _chosenUrl;
|
||||
Ui::PeerUserpicView _userpic;
|
||||
|
@ -191,7 +206,7 @@ PreviewWrap::PreviewWrap(
|
|||
const auto session = &_history->session();
|
||||
session->data().viewRepaintRequest(
|
||||
) | rpl::start_with_next([=](not_null<const Element*> view) {
|
||||
if (view == _element.get()) {
|
||||
if (_views.contains(view)) {
|
||||
update();
|
||||
}
|
||||
}, lifetime());
|
||||
|
@ -221,26 +236,92 @@ PreviewWrap::PreviewWrap(
|
|||
|
||||
PreviewWrap::~PreviewWrap() {
|
||||
_selection.reset(TextSelection());
|
||||
base::take(_views);
|
||||
clear(base::take(_entries));
|
||||
}
|
||||
|
||||
void PreviewWrap::clear(std::vector<Entry> entries) {
|
||||
_elementLifetime.destroy();
|
||||
_element = nullptr;
|
||||
if (_draftItem) {
|
||||
_draftItem->destroy();
|
||||
for (auto &entry : entries) {
|
||||
entry.view = nullptr;
|
||||
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(
|
||||
const SelectedQuote "e) {
|
||||
_selection.reset(TextSelection());
|
||||
|
||||
auto was = base::take(_entries);
|
||||
const auto wasViews = base::take(_views);
|
||||
const auto item = quote.item;
|
||||
const auto group = item->history()->owner().groups().find(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;
|
||||
|
||||
if (const auto was = base::take(_draftItem)) {
|
||||
was->destroy();
|
||||
}
|
||||
clear(std::move(was));
|
||||
|
||||
const auto media = item->media();
|
||||
_onlyMessageText = media
|
||||
|
@ -249,12 +330,13 @@ rpl::producer<SelectedQuote> PreviewWrap::showQuoteSelector(
|
|||
|| (!media->photo() && !media->document()));
|
||||
_section = Section::Reply;
|
||||
|
||||
initElement();
|
||||
initElements();
|
||||
|
||||
_selection = _element->selectionFromQuote(quote);
|
||||
const auto view = _entries.back().view.get();
|
||||
_selection = view->selectionFromQuote(quote);
|
||||
return _selection.value(
|
||||
) | rpl::map([=](TextSelection selection) {
|
||||
if (const auto result = _element->selectedQuote(selection)) {
|
||||
if (const auto result = view->selectedQuote(selection)) {
|
||||
return result;
|
||||
}
|
||||
return SelectedQuote{ item };
|
||||
|
@ -267,13 +349,11 @@ rpl::producer<QString> PreviewWrap::showLinkSelector(
|
|||
const std::vector<MessageLinkRange> &links,
|
||||
const QString &usedLink) {
|
||||
_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;
|
||||
_draftItem = _history->addNewLocalMessage({
|
||||
const auto item = _history->addNewLocalMessage({
|
||||
.id = _history->nextNonHistoryEntryId(),
|
||||
.flags = (MessageFlag::FakeHistoryItem
|
||||
| MessageFlag::Outgoing
|
||||
|
@ -299,13 +379,15 @@ rpl::producer<QString> PreviewWrap::showLinkSelector(
|
|||
MTP_long(webpage.id),
|
||||
MTP_string(webpage.url),
|
||||
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;
|
||||
_symbol = _selectionStartSymbol = 0;
|
||||
_afterSymbol = _selectionStartAfterSymbol = false;
|
||||
_section = Section::Link;
|
||||
|
||||
initElement();
|
||||
initElements();
|
||||
highlightUsedLink(message, usedLink, links);
|
||||
|
||||
return _chosenUrl.events();
|
||||
|
@ -338,7 +420,8 @@ void PreviewWrap::highlightUsedLink(
|
|||
text = text.mid(0, text.size() - 1);
|
||||
--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,
|
||||
.onlyMessageText = true,
|
||||
});
|
||||
|
@ -353,30 +436,31 @@ void PreviewWrap::highlightUsedLink(
|
|||
}
|
||||
|
||||
void PreviewWrap::paintEvent(QPaintEvent *e) {
|
||||
if (!_element) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto p = Painter(this);
|
||||
|
||||
p.translate(_position);
|
||||
|
||||
auto context = _theme->preparePaintContext(
|
||||
_style.get(),
|
||||
rect(),
|
||||
e->rect(),
|
||||
!window()->isActiveWindow());
|
||||
context.outbg = _element->hasOutLayout();
|
||||
context.selection = _selecting
|
||||
? resolveNewSelection()
|
||||
: _selection.current();
|
||||
for (const auto &entry : _entries) {
|
||||
context.outbg = entry.view->hasOutLayout();
|
||||
context.selection = _selecting
|
||||
? resolveNewSelection()
|
||||
: _selection.current();
|
||||
|
||||
p.translate(_position);
|
||||
_element->draw(p, context);
|
||||
entry.view->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()
|
||||
- _element->marginBottom()
|
||||
- _element->marginTop();
|
||||
const auto item = _element->data();
|
||||
- top->marginBottom()
|
||||
- top->marginTop();
|
||||
const auto item = top->data();
|
||||
const auto userpicTop = userpicBottom - st::msgPhotoSize;
|
||||
if (const auto from = item->displayFrom()) {
|
||||
from->paintUserpicLeft(
|
||||
|
@ -415,7 +499,7 @@ void PreviewWrap::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
|
||||
void PreviewWrap::leaveEventHook(QEvent *e) {
|
||||
if (!_element || !_over) {
|
||||
if (!_over) {
|
||||
return;
|
||||
}
|
||||
_over = false;
|
||||
|
@ -427,7 +511,7 @@ void PreviewWrap::leaveEventHook(QEvent *e) {
|
|||
}
|
||||
|
||||
void PreviewWrap::mouseMoveEvent(QMouseEvent *e) {
|
||||
if (!_element) {
|
||||
if (_entries.empty()) {
|
||||
return;
|
||||
}
|
||||
using Flag = Ui::Text::StateRequest::Flag;
|
||||
|
@ -438,7 +522,16 @@ void PreviewWrap::mouseMoveEvent(QMouseEvent *e) {
|
|||
.onlyMessageText = (_section == Section::Link || _onlyMessageText),
|
||||
};
|
||||
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;
|
||||
const auto text = (_section == Section::Reply)
|
||||
&& (resolved.cursor == CursorState::Text);
|
||||
|
@ -518,27 +611,25 @@ void PreviewWrap::mouseDoubleClickEvent(QMouseEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void PreviewWrap::initElement() {
|
||||
_elementLifetime.destroy();
|
||||
|
||||
if (!_element) {
|
||||
return;
|
||||
void PreviewWrap::initElements() {
|
||||
for (auto &entry : _entries) {
|
||||
entry.view->initDimensions();
|
||||
}
|
||||
_element->initDimensions();
|
||||
|
||||
widthValue(
|
||||
) | rpl::filter([=](int width) {
|
||||
return width > st::msgMinWidth;
|
||||
}) | rpl::start_with_next([=](int width) {
|
||||
const auto height = _position.y()
|
||||
+ _element->resizeGetHeight(width)
|
||||
+ st::msgMargin.top();
|
||||
auto height = _position.y();
|
||||
for (const auto &entry : _entries) {
|
||||
height += entry.view->resizeGetHeight(width);
|
||||
}
|
||||
height += st::msgMargin.top();
|
||||
resize(width, height);
|
||||
}, _elementLifetime);
|
||||
}
|
||||
|
||||
TextSelection PreviewWrap::resolveNewSelection() const {
|
||||
if (_section != Section::Reply) {
|
||||
if (_section != Section::Reply || _entries.empty()) {
|
||||
return TextSelection();
|
||||
}
|
||||
const auto make = [](uint16 symbol, bool afterSymbol) {
|
||||
|
@ -551,7 +642,7 @@ TextSelection PreviewWrap::resolveNewSelection() const {
|
|||
const auto result = (first <= second)
|
||||
? TextSelection{ first, second }
|
||||
: TextSelection{ second, first };
|
||||
return _element->adjustSelection(result, _selectType);
|
||||
return _entries.back().view->adjustSelection(result, _selectType);
|
||||
}
|
||||
|
||||
void PreviewWrap::startSelection(TextSelectType type) {
|
||||
|
@ -607,9 +698,10 @@ void DraftOptionsBox(
|
|||
|
||||
const auto &draft = args.draft;
|
||||
struct State {
|
||||
rpl::variable<Section> shown;
|
||||
rpl::variable<Section> shown = Section::Link;
|
||||
rpl::lifetime shownLifetime;
|
||||
rpl::variable<SelectedQuote> quote;
|
||||
Data::ResolvedForwardDraft forward;
|
||||
Data::WebPageDraft webpage;
|
||||
WebPageData *preview = nullptr;
|
||||
QString link;
|
||||
|
@ -619,41 +711,90 @@ void DraftOptionsBox(
|
|||
Fn<void(const QString &link, WebPageData *page)> performSwitch;
|
||||
Fn<void(const QString &link, bool force)> requestAndSwitch;
|
||||
rpl::lifetime resolveLifetime;
|
||||
|
||||
Fn<void()> rebuild;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
state->link = args.usedLink;
|
||||
state->quote = SelectedQuote{
|
||||
replyItem,
|
||||
draft.reply.quote,
|
||||
draft.reply.quoteOffset,
|
||||
};
|
||||
state->forward = std::move(args.forward);
|
||||
state->webpage = draft.webpage;
|
||||
state->preview = previewData;
|
||||
state->shown = previewData ? Section::Link : Section::Reply;
|
||||
if (replyItem && previewData) {
|
||||
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({
|
||||
tr::lng_reply_header_short(tr::now),
|
||||
tr::lng_link_header_short(tr::now),
|
||||
});
|
||||
state->tabs->setActiveSectionFast(1);
|
||||
state->tabs->sectionActivated(
|
||||
) | rpl::start_with_next([=](int section) {
|
||||
state->shown = section ? Section::Link : Section::Reply;
|
||||
}, box->lifetime());
|
||||
} else {
|
||||
box->setTitle(previewData
|
||||
? tr::lng_link_options_header()
|
||||
: draft.reply.quote.empty()
|
||||
? tr::lng_reply_options_header()
|
||||
: tr::lng_reply_options_quote());
|
||||
}
|
||||
|
||||
state->rebuild = [=] {
|
||||
const auto hasLink = (state->preview != nullptr);
|
||||
const auto hasReply = (state->quote.current().item != nullptr);
|
||||
const auto hasForward = !state->forward.items.empty();
|
||||
if (!hasLink && !hasReply && !hasForward) {
|
||||
box->closeBox();
|
||||
return;
|
||||
}
|
||||
const auto section = state->shown.current();
|
||||
const auto changeSection = (section == Section::Link)
|
||||
? !hasLink
|
||||
: (section == Section::Reply)
|
||||
? !hasReply
|
||||
: !hasForward;
|
||||
const auto now = !changeSection
|
||||
? section
|
||||
: hasLink
|
||||
? Section::Link
|
||||
: hasReply
|
||||
? Section::Reply
|
||||
: Section::Forward;
|
||||
auto labels = std::vector<QString>();
|
||||
auto indices = base::flat_map<Section, int>();
|
||||
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(
|
||||
object_ptr<Ui::VerticalLayout>(box));
|
||||
|
@ -675,9 +816,17 @@ void DraftOptionsBox(
|
|||
};
|
||||
const auto finish = [=](
|
||||
FullReplyTo result,
|
||||
Data::WebPageDraft webpage) {
|
||||
Data::WebPageDraft webpage,
|
||||
std::optional<Data::ForwardOptions> options) {
|
||||
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()) {
|
||||
strong->closeBox();
|
||||
}
|
||||
|
@ -716,7 +865,7 @@ void DraftOptionsBox(
|
|||
st::settingsAttentionButtonWithIcon,
|
||||
{ &st::menuIconDeleteAttention }
|
||||
)->setClickedCallback([=] {
|
||||
finish({}, state->webpage);
|
||||
finish({}, state->webpage, state->forward.options);
|
||||
});
|
||||
|
||||
if (!item->originalText().empty()) {
|
||||
|
@ -774,7 +923,8 @@ void DraftOptionsBox(
|
|||
st::settingsAttentionButtonWithIcon,
|
||||
{ &st::menuIconDeleteAttention }
|
||||
)->setClickedCallback([=] {
|
||||
finish(resolveReply(), { .removed = true });
|
||||
const auto options = state->forward.options;
|
||||
finish(resolveReply(), { .removed = true }, options);
|
||||
});
|
||||
|
||||
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;
|
||||
state->performSwitch = [=](const QString &link, WebPageData *page) {
|
||||
const auto now = base::unixtime::now();
|
||||
|
@ -847,20 +1083,27 @@ void DraftOptionsBox(
|
|||
state->shown.value() | rpl::start_with_next([=](Section shown) {
|
||||
bottom->clear();
|
||||
state->shownLifetime.destroy();
|
||||
if (shown == Section::Reply) {
|
||||
state->quote = state->wrap->showQuoteSelector(
|
||||
state->quote.current());
|
||||
setupReplyActions();
|
||||
} else {
|
||||
state->wrap->showLinkSelector(
|
||||
draft.textWithTags,
|
||||
state->webpage,
|
||||
linkRanges,
|
||||
state->link
|
||||
) | rpl::start_with_next([=](QString link) {
|
||||
switchTo(link);
|
||||
}, state->shownLifetime);
|
||||
setupLinkActions();
|
||||
switch (shown) {
|
||||
case Section::Reply: {
|
||||
state->quote = state->wrap->showQuoteSelector(
|
||||
state->quote.current());
|
||||
setupReplyActions();
|
||||
} break;
|
||||
case Section::Link: {
|
||||
state->wrap->showLinkSelector(
|
||||
draft.textWithTags,
|
||||
state->webpage,
|
||||
linkRanges,
|
||||
state->link
|
||||
) | rpl::start_with_next([=](QString link) {
|
||||
switchTo(link);
|
||||
}, state->shownLifetime);
|
||||
setupLinkActions();
|
||||
} break;
|
||||
case Section::Forward: {
|
||||
state->wrap->showForwardSelector(state->forward);
|
||||
setupForwardActions();
|
||||
} break;
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
|
@ -879,7 +1122,8 @@ void DraftOptionsBox(
|
|||
.text = { tr::lng_reply_quote_long_text(tr::now) },
|
||||
});
|
||||
} else {
|
||||
finish(resolveReply(), state->webpage);
|
||||
const auto options = state->forward.options;
|
||||
finish(resolveReply(), state->webpage, options);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -887,31 +1131,29 @@ void DraftOptionsBox(
|
|||
box->closeBox();
|
||||
});
|
||||
|
||||
if (replyItem) {
|
||||
args.show->session().data().itemRemoved(
|
||||
) | rpl::filter([=](not_null<const HistoryItem*> removed) {
|
||||
const auto current = state->quote.current().item;
|
||||
if ((removed == replyItem) || (removed == current)) {
|
||||
return true;
|
||||
}
|
||||
const auto group = current->history()->owner().groups().find(
|
||||
current);
|
||||
return (group && ranges::contains(group->items, removed));
|
||||
}) | rpl::start_with_next([=] {
|
||||
if (previewData) {
|
||||
state->tabs = nullptr;
|
||||
box->setPinnedToTopContent(
|
||||
object_ptr<Ui::RpWidget>(nullptr));
|
||||
box->setNoContentMargin(false);
|
||||
box->setTitle(state->quote.current().text.empty()
|
||||
? tr::lng_reply_options_header()
|
||||
: tr::lng_reply_options_quote());
|
||||
state->shown = Section::Link;
|
||||
} else {
|
||||
box->closeBox();
|
||||
}
|
||||
}, box->lifetime());
|
||||
}
|
||||
args.show->session().data().itemRemoved(
|
||||
) | rpl::start_with_next([=](not_null<const HistoryItem*> removed) {
|
||||
const auto inReply = (state->quote.current().item == removed);
|
||||
if (inReply) {
|
||||
state->quote = SelectedQuote();
|
||||
}
|
||||
const auto i = ranges::find(state->forward.items, removed);
|
||||
const auto inForward = (i != end(state->forward.items));
|
||||
if (inForward) {
|
||||
state->forward.items.erase(i);
|
||||
}
|
||||
if (inReply || inForward) {
|
||||
state->rebuild();
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
args.show->session().data().itemViewRefreshRequest(
|
||||
) | rpl::start_with_next([=](not_null<const HistoryItem*> item) {
|
||||
if (state->wrap->hasViewForItem(item)) {
|
||||
state->rebuild();
|
||||
}
|
||||
}, box->lifetime());
|
||||
|
||||
}
|
||||
|
||||
struct AuthorSelector {
|
||||
|
@ -1165,7 +1407,7 @@ void EditDraftOptions(EditDraftOptionsArgs &&args) {
|
|||
&& !previewDataRaw->failed)
|
||||
? previewDataRaw
|
||||
: nullptr;
|
||||
if (!replyItem && !previewData) {
|
||||
if (!replyItem && !previewData && args.forward.items.empty()) {
|
||||
return;
|
||||
}
|
||||
args.show->show(
|
||||
|
|
|
@ -29,9 +29,10 @@ struct EditDraftOptionsArgs {
|
|||
not_null<History*> history;
|
||||
Data::Draft draft;
|
||||
QString usedLink;
|
||||
Data::ResolvedForwardDraft forward;
|
||||
std::vector<MessageLinkRange> links;
|
||||
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()> clearOldDraft;
|
||||
};
|
||||
|
|
|
@ -44,19 +44,6 @@ constexpr auto kUnknownVersion = -1;
|
|||
constexpr auto kNameWithCaptionsVersion = -2;
|
||||
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
|
||||
|
||||
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 {
|
||||
return _data.items;
|
||||
}
|
||||
|
@ -236,63 +227,17 @@ bool ForwardPanel::empty() const {
|
|||
return _data.items.empty();
|
||||
}
|
||||
|
||||
void ForwardPanel::editOptions(std::shared_ptr<ChatHelpers::Show> show) {
|
||||
using Options = Data::ForwardOptions;
|
||||
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();
|
||||
void ForwardPanel::applyOptions(Data::ForwardOptions options) {
|
||||
if (_data.items.empty()) {
|
||||
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() {
|
||||
|
@ -485,7 +430,19 @@ void EditWebPageOptions(
|
|||
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
|
||||
|
|
|
@ -47,9 +47,10 @@ public:
|
|||
|
||||
[[nodiscard]] rpl::producer<> itemsUpdated() const;
|
||||
|
||||
void editOptions(std::shared_ptr<ChatHelpers::Show> show);
|
||||
void applyOptions(Data::ForwardOptions options);
|
||||
void editToNextOption();
|
||||
|
||||
[[nodiscard]] const Data::ResolvedForwardDraft &draft() const;
|
||||
[[nodiscard]] const HistoryItemsList &items() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
|
@ -83,4 +84,6 @@ void EditWebPageOptions(
|
|||
Data::WebPageDraft draft,
|
||||
Fn<void(Data::WebPageDraft)> done);
|
||||
|
||||
[[nodiscard]] bool HasOnlyForcedForwardedInfo(const HistoryItemsList &list);
|
||||
|
||||
} // namespace HistoryView::Controls
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,11 +28,4 @@ void FillForwardOptions(
|
|||
Fn<void(ForwardOptions)> optionsChanged,
|
||||
rpl::lifetime &lifetime);
|
||||
|
||||
void ForwardOptionsBox(
|
||||
not_null<GenericBox*> box,
|
||||
int count,
|
||||
ForwardOptions options,
|
||||
Fn<void(ForwardOptions)> optionsChanged,
|
||||
Fn<void()> changeRecipient);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -150,6 +150,10 @@ menuIconAbove: icon {{ "menu/link_above", menuIconColor }};
|
|||
menuIconBelow: icon {{ "menu/link_below", menuIconColor }};
|
||||
menuIconEnlarge: icon {{ "menu/link_enlarge", 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 }};
|
||||
menuIconAsMessages: icon {{ "menu/mode_messages", menuIconColor }};
|
||||
menuIconTagFilter: icon{{ "menu/tag_filter", menuIconColor }};
|
||||
|
|