Allow replying inside replies section.

This commit is contained in:
John Preston 2020-09-01 10:07:37 +04:00
parent f22a804220
commit 17549ad5ea
7 changed files with 224 additions and 61 deletions

View File

@ -67,35 +67,37 @@ WebPageText ProcessWebPageData(WebPageData *page) {
} // namespace
class FieldHeader : public Ui::RpWidget {
class FieldHeader final : public Ui::RpWidget {
public:
FieldHeader(QWidget *parent, not_null<Data::Session*> data);
void init();
void editMessage(FullMsgId edit);
void editMessage(FullMsgId id);
void replyToMessage(FullMsgId id);
void previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
rpl::producer<WebPageData*> page);
bool isDisplayed() const;
bool isEditingMessage() const;
rpl::producer<FullMsgId> editMsgId() const;
rpl::producer<FullMsgId> scrollToItemRequests() const;
MessageToEdit queryToEdit();
WebPageId webPageId() const;
[[nodiscard]] bool isDisplayed() const;
[[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] FullMsgId replyingToMessage() const;
[[nodiscard]] rpl::producer<FullMsgId> editMsgId() const;
[[nodiscard]] rpl::producer<FullMsgId> scrollToItemRequests() const;
[[nodiscard]] MessageToEdit queryToEdit();
[[nodiscard]] WebPageId webPageId() const;
rpl::producer<bool> visibleChanged();
protected:
[[nodiscard]] rpl::producer<bool> visibleChanged();
private:
void updateControlsGeometry(QSize size);
void updateVisible();
void setShownMessage(HistoryItem *message);
void updateShownMessageText();
void paintWebPage(Painter &p);
void paintEditMessage(Painter &p);
void paintEditOrReplyToMessage(Painter &p);
struct Preview {
WebPageData *data = nullptr;
@ -110,8 +112,13 @@ private:
bool hasPreview() const;
Ui::Text::String _editMsgText;
rpl::variable<FullMsgId> _editMsgId;
rpl::variable<FullMsgId> _replyToId;
HistoryItem *_shownMessage = nullptr;
Ui::Text::String _shownMessageName;
Ui::Text::String _shownMessageText;
int _shownMessageNameVersion = -1;
const not_null<Data::Session*> _data;
const not_null<Ui::IconButton*> _cancel;
@ -141,37 +148,46 @@ void FieldHeader::init() {
Painter p(this);
p.fillRect(rect(), st::historyComposeAreaBg);
const auto position = st::historyReplyIconPosition;
if (isEditingMessage()) {
const auto position = st::historyReplyIconPosition;
st::historyEditIcon.paint(p, position, width());
} else if (replyingToMessage()) {
st::historyReplyIcon.paint(p, position, width());
}
(!ShowWebPagePreview(_preview.data) || *leftIconPressed)
? paintEditMessage(p)
? paintEditOrReplyToMessage(p)
: paintWebPage(p);
}, lifetime());
const auto checkPreview = [=](not_null<const HistoryItem*> item) {
_preview = {};
if (const auto media = item->media()) {
if (const auto page = media->webpage()) {
const auto preview = ProcessWebPageData(page);
_title = preview.title;
_description = preview.description;
_preview.data = page;
}
}
};
_editMsgId.value(
) | rpl::start_with_next([=] {
updateVisible();
if (const auto item = _data->message(_editMsgId.current())) {
_editMsgText.setText(
st::messageTextStyle,
item->inReplyText(),
Ui::DialogTextOptions());
checkPreview(item);
) | rpl::start_with_next([=](FullMsgId value) {
const auto shown = value ? value : _replyToId.current();
setShownMessage(_data->message(shown));
}, lifetime());
_replyToId.value(
) | rpl::start_with_next([=](FullMsgId value) {
if (!_editMsgId.current()) {
setShownMessage(_data->message(value));
}
}, lifetime());
_data->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Edited
| Data::MessageUpdate::Flag::Destroyed
) | rpl::filter([=](const Data::MessageUpdate &update) {
return (update.item == _shownMessage);
}) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.flags & Data::MessageUpdate::Flag::Destroyed) {
if (_editMsgId.current() == update.item->fullId()) {
editMessage({});
}
if (_replyToId.current() == update.item->fullId()) {
replyToMessage({});
}
} else {
updateShownMessageText();
}
}, lifetime());
@ -179,8 +195,10 @@ void FieldHeader::init() {
if (hasPreview()) {
_preview = {};
update();
} else {
_editMsgId = {};
} else if (_editMsgId.current()) {
editMessage({});
} else if (_replyToId.current()) {
replyToMessage({});
}
updateVisible();
});
@ -246,6 +264,46 @@ void FieldHeader::init() {
}, lifetime());
}
void FieldHeader::updateShownMessageText() {
Expects(_shownMessage != nullptr);
_shownMessageText.setText(
st::messageTextStyle,
_shownMessage->inReplyText(),
Ui::DialogTextOptions());
}
void FieldHeader::setShownMessage(HistoryItem *item) {
_shownMessage = item;
if (item) {
updateShownMessageText();
if (item->fullId() == _editMsgId.current()) {
_preview = {};
if (const auto media = item->media()) {
if (const auto page = media->webpage()) {
const auto preview = ProcessWebPageData(page);
_title = preview.title;
_description = preview.description;
_preview.data = page;
}
}
}
} else {
_shownMessageText.clear();
}
if (isEditingMessage()) {
_shownMessageName.setText(
st::msgNameStyle,
tr::lng_edit_message(tr::now),
Ui::NameTextOptions());
} else {
_shownMessageName.clear();
_shownMessageNameVersion = -1;
}
updateVisible();
update();
}
void FieldHeader::previewRequested(
rpl::producer<QString> title,
rpl::producer<QString> description,
@ -315,23 +373,43 @@ void FieldHeader::paintWebPage(Painter &p) {
elidedWidth);
}
void FieldHeader::paintEditMessage(Painter &p) {
void FieldHeader::paintEditOrReplyToMessage(Painter &p) {
Expects(_shownMessage != nullptr);
const auto replySkip = st::historyReplySkip;
const auto availableWidth = width()
- replySkip
- _cancel->width()
- st::msgReplyPadding.right();
if (!isEditingMessage()) {
const auto user = _shownMessage->displayFrom()
? _shownMessage->displayFrom()
: _shownMessage->author().get();
if (user->nameVersion > _shownMessageNameVersion) {
_shownMessageName.setText(
st::msgNameStyle,
user->name,
Ui::NameTextOptions());
_shownMessageNameVersion = user->nameVersion;
}
}
p.setPen(st::historyReplyNameFg);
p.setFont(st::msgServiceNameFont);
p.drawTextLeft(
_shownMessageName.drawElided(
p,
replySkip,
st::msgReplyPadding.top(),
width(),
tr::lng_edit_message(tr::now));
availableWidth);
p.setPen(st::historyComposeAreaFg);
p.setTextPalette(st::historyComposeAreaPalette);
_editMsgText.drawElided(
_shownMessageText.drawElided(
p,
replySkip,
st::msgReplyPadding.top() + st::msgServiceNameFont->height,
width() - replySkip - _cancel->width() - st::msgReplyPadding.right());
availableWidth);
p.restoreTextPalette();
}
@ -345,13 +423,17 @@ rpl::producer<bool> FieldHeader::visibleChanged() {
}
bool FieldHeader::isDisplayed() const {
return isEditingMessage() || hasPreview();
return isEditingMessage() || replyingToMessage() || hasPreview();
}
bool FieldHeader::isEditingMessage() const {
return !!_editMsgId.current();
}
FullMsgId FieldHeader::replyingToMessage() const {
return _replyToId.current();
}
bool FieldHeader::hasPreview() const {
return ShowWebPagePreview(_preview.data);
}
@ -368,6 +450,10 @@ void FieldHeader::editMessage(FullMsgId id) {
_editMsgId = id;
}
void FieldHeader::replyToMessage(FullMsgId id) {
_replyToId = id;
}
rpl::producer<FullMsgId> FieldHeader::editMsgId() const {
return _editMsgId.value();
}
@ -550,6 +636,7 @@ TextWithTags ComposeControls::getTextWithAppliedMarkdown() const {
void ComposeControls::clear() {
setText(TextWithTags());
cancelReplyMessage();
}
void ComposeControls::setText(const TextWithTags &textWithTags) {
@ -863,15 +950,24 @@ void ComposeControls::updateHeight() {
}
}
void ComposeControls::editMessage(FullMsgId edit) {
void ComposeControls::editMessage(FullMsgId id) {
cancelEditMessage();
_header->editMessage(std::move(edit));
_header->editMessage(id);
}
void ComposeControls::cancelEditMessage() {
_header->editMessage({});
}
void ComposeControls::replyToMessage(FullMsgId id) {
cancelReplyMessage();
_header->replyToMessage(id);
}
void ComposeControls::cancelReplyMessage() {
_header->replyToMessage({});
}
void ComposeControls::initWebpageProcess() {
Expects(_history);
const auto peer = _history->peer;
@ -1060,4 +1156,8 @@ bool ComposeControls::isEditingMessage() const {
return _header->isEditingMessage();
}
FullMsgId ComposeControls::replyingToMessage() const {
return _header->replyingToMessage();
}
} // namespace HistoryView

View File

@ -104,15 +104,19 @@ public:
const Window::SectionShow &params);
bool returnTabbedSelector();
bool isEditingMessage() const;
[[nodiscard]] bool isEditingMessage() const;
[[nodiscard]] FullMsgId replyingToMessage() const;
void showForGrab();
void showStarted();
void showFinished();
void editMessage(FullMsgId edit);
void editMessage(FullMsgId id);
void cancelEditMessage();
void replyToMessage(FullMsgId id);
void cancelReplyMessage();
[[nodiscard]] TextWithTags getTextWithAppliedMarkdown() const;
[[nodiscard]] WebPageId webPageId() const;
void clear();

View File

@ -75,7 +75,7 @@ MsgId ItemIdAcrossData(not_null<HistoryItem*> item) {
return session->data().scheduledMessages().lookupId(item);
}
bool HasEditScheduledMessageAction(const ContextMenuRequest &request) {
bool HasEditMessageAction(const ContextMenuRequest &request) {
const auto item = request.item;
if (!item
|| item->isSending()
@ -415,7 +415,8 @@ bool AddSendNowMessageAction(
bool AddRescheduleMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request) {
if (!HasEditScheduledMessageAction(request)) {
if (!HasEditMessageAction(request)
|| !request.item->isScheduled()) {
return false;
}
const auto item = request.item;
@ -458,11 +459,33 @@ bool AddRescheduleMessageAction(
return true;
}
bool AddReplyToMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
const auto item = request.item;
if (!item
|| !IsServerMsgId(item->id)
|| !item->history()->peer->canWrite()) {
return false;
}
const auto owner = &item->history()->owner();
const auto itemId = item->fullId();
menu->addAction(tr::lng_context_reply_msg(tr::now), [=] {
const auto item = owner->message(itemId);
if (!item) {
return;
}
list->replyToMessageRequestNotify(item->fullId());
});
return true;
}
bool AddEditMessageAction(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
if (!HasEditScheduledMessageAction(request)) {
if (!HasEditMessageAction(request)) {
return false;
}
const auto item = request.item;
@ -630,11 +653,18 @@ void AddSelectionAction(
}
}
void AddTopMessageActions(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
AddReplyToMessageAction(menu, request, list);
AddEditMessageAction(menu, request, list);
}
void AddMessageActions(
not_null<Ui::PopupMenu*> menu,
const ContextMenuRequest &request,
not_null<ListWidget*> list) {
AddEditMessageAction(menu, request, list);
AddPostLinkAction(menu, request);
AddForwardAction(menu, request, list);
AddSendNowAction(menu, request, list);
@ -695,6 +725,7 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
});
}
AddTopMessageActions(result, request, list);
if (linkPhoto) {
AddPhotoActions(result, photo);
} else if (linkDocument) {

View File

@ -2585,6 +2585,14 @@ void ListWidget::editMessageRequestNotify(FullMsgId item) {
_requestedToEditMessage.fire(std::move(item));
}
rpl::producer<FullMsgId> ListWidget::replyToMessageRequested() const {
return _requestedToReplyToMessage.events();
}
void ListWidget::replyToMessageRequestNotify(FullMsgId item) {
_requestedToReplyToMessage.fire(std::move(item));
}
ListWidget::~ListWidget() = default;
} // namespace HistoryView

View File

@ -188,6 +188,8 @@ public:
rpl::producer<FullMsgId> editMessageRequested() const;
void editMessageRequestNotify(FullMsgId item);
rpl::producer<FullMsgId> replyToMessageRequested() const;
void replyToMessageRequestNotify(FullMsgId item);
// ElementDelegate interface.
Context elementContext() override;
@ -523,6 +525,7 @@ private:
base::Timer _highlightTimer;
rpl::event_stream<FullMsgId> _requestedToEditMessage;
rpl::event_stream<FullMsgId> _requestedToReplyToMessage;
rpl::lifetime _viewerLifetime;

View File

@ -158,6 +158,11 @@ RepliesWidget::RepliesWidget(
}
}, _inner->lifetime());
_inner->replyToMessageRequested(
) | rpl::start_with_next([=](auto fullId) {
_composeControls->replyToMessage(fullId);
}, _inner->lifetime());
_history->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
) | rpl::filter([=](const Data::MessageUpdate &update) {
@ -390,6 +395,7 @@ bool RepliesWidget::confirmSendingFiles(
SendMenu::Type::Disabled); // #TODO replies schedule
//_field->setTextWithTags({});
const auto replyTo = replyToId();
box->setConfirmedCallback(crl::guard(this, [=](
Storage::PreparedList &&list,
SendFilesWay way,
@ -409,7 +415,7 @@ bool RepliesWidget::confirmSendingFiles(
std::move(list),
type,
std::move(caption),
MsgId(_rootId),//replyToId(), // #TODO replies reply
replyTo,
options,
album);
}));
@ -470,7 +476,7 @@ void RepliesWidget::uploadFilesAfterConfirmation(
return;
}
auto action = Api::SendAction(_history);
action.replyTo = _rootId;// replyTo;// #TODO replies reply
action.replyTo = replyTo ? replyTo : _rootId;
action.options = options;
session().api().sendFiles(
std::move(list),
@ -536,7 +542,7 @@ void RepliesWidget::uploadFile(
SendMediaType type) {
// #TODO replies schedule
auto action = Api::SendAction(_history);
action.replyTo = _rootId;// #TODO replies reply
action.replyTo = replyToId();
session().api().sendFile(fileContent, type, action);
}
@ -595,7 +601,7 @@ void RepliesWidget::send(Api::SendOptions options) {
auto message = ApiWrap::MessageToSend(_history);
message.textWithTags = _composeControls->getTextWithAppliedMarkdown();
message.action.options = options;
message.action.replyTo = _rootId;// replyToId();// #TODO replies reply
message.action.replyTo = replyToId();
message.webPageId = webPageId;
//const auto error = GetErrorTextForSending(
@ -717,7 +723,7 @@ bool RepliesWidget::sendExistingDocument(
}
auto message = Api::MessageToSend(_history);
message.action.replyTo = _rootId;// replyToId();// #TODO replies reply
message.action.replyTo = replyToId();
message.action.options = options;
Api::SendExistingDocument(std::move(message), document);
@ -729,6 +735,7 @@ bool RepliesWidget::sendExistingDocument(
// onCloudDraftSave(); // won't be needed if SendInlineBotResult will clear the cloud draft
//}
_composeControls->cancelReplyMessage();
_composeControls->hidePanelsAnimated();
_composeControls->focus();
return true;
@ -757,10 +764,11 @@ bool RepliesWidget::sendExistingPhoto(
}
auto message = Api::MessageToSend(_history);
message.action.replyTo = _rootId;// replyToId();// #TODO replies reply
message.action.replyTo = replyToId();
message.action.options = options;
Api::SendExistingPhoto(std::move(message), photo);
_composeControls->cancelReplyMessage();
_composeControls->hidePanelsAnimated();
_composeControls->focus();
return true;
@ -788,7 +796,7 @@ void RepliesWidget::sendInlineResult(
not_null<UserData*> bot,
Api::SendOptions options) {
auto action = Api::SendAction(_history);
action.replyTo = _rootId;// replyToId();// #TODO replies reply
action.replyTo = replyToId();
action.options = options;
action.generateLocal = true;
session().api().sendInlineResult(bot, result, action);
@ -823,6 +831,11 @@ SendMenu::Type RepliesWidget::sendMenuType() const {
: SendMenu::Type::Scheduled;
}
MsgId RepliesWidget::replyToId() const {
const auto custom = _composeControls->replyingToMessage().msg;
return custom ? custom : _rootId;
}
void RepliesWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] {
scrollDownClicked();
@ -1188,6 +1201,9 @@ void RepliesWidget::listCancelRequest() {
if (_composeControls->isEditingMessage()) {
_composeControls->cancelEditMessage();
return;
} else if (_composeControls->replyingToMessage()) {
_composeControls->cancelReplyMessage();
return;
}
controller()->showBackFromStack();
}
@ -1225,8 +1241,8 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) {
if (item.canDelete) {
++state.canDeleteCount;
}
if (item.canSendNow) {
++state.canSendNowCount;
if (item.canForward) {
++state.canForwardCount;
}
}
_topBar->showSelected(state);

View File

@ -166,6 +166,7 @@ private:
mtpRequestId *const saveEditMsgRequestId);
void chooseAttach();
[[nodiscard]] SendMenu::Type sendMenuType() const;
[[nodiscard]] MsgId replyToId() const;
void pushReplyReturn(not_null<HistoryItem*> item);
void computeCurrentReplyReturn();