Implement replies list request.

This commit is contained in:
John Preston 2020-08-28 14:01:55 +04:00
parent 437c9320cd
commit 00cdae0369
24 changed files with 1991 additions and 20 deletions

View File

@ -467,6 +467,8 @@ PRIVATE
data/data_poll.h
data/data_pts_waiter.cpp
data/data_pts_waiter.h
data/data_replies_list.cpp
data/data_replies_list.h
data/data_reply_preview.cpp
data/data_reply_preview.h
data/data_search_controller.cpp
@ -588,6 +590,8 @@ PRIVATE
history/view/history_view_message.cpp
history/view/history_view_message.h
history/view/history_view_object.h
history/view/history_view_replies_section.cpp
history/view/history_view_replies_section.h
history/view/history_view_schedule_box.cpp
history/view/history_view_schedule_box.h
history/view/history_view_scheduled_section.cpp

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

View File

@ -1344,6 +1344,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_scheduled_send_now_many#one" = "Send {count} message now?";
"lng_scheduled_send_now_many#other" = "Send {count} messages now?";
"lng_replies_view#one" = "View {count} Reply";
"lng_replies_view#other" = "View {count} Replies";
"lng_replies_view_thread" = "View Thread";
"lng_replies_header#one" = "{count} reply";
"lng_replies_header#other" = "{count} replies";
"lng_replies_header_none" = "No replies";
"lng_replies_send_placeholder" = "Reply";
"lng_archived_name" = "Archived chats";
"lng_archived_add" = "Archive";
"lng_archived_remove" = "Unarchive";

View File

@ -272,8 +272,10 @@ void BoxController::prepare() {
}, lifetime());
session().changes().messageUpdates(
Data::MessageUpdate::Flag::CallAdded
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
Data::MessageUpdate::Flag::NewAdded
) | rpl::filter([=](const Data::MessageUpdate &update) {
return (update.item->media()->call() != nullptr);
}) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
insertRow(update.item, InsertWay::Prepend);
}, lifetime());

View File

@ -133,11 +133,12 @@ struct MessageUpdate {
Destroyed = (1 << 1),
DialogRowRepaint = (1 << 2),
DialogRowRefresh = (1 << 3),
CallAdded = (1 << 4),
NewAdded = (1 << 4),
ReplyMarkup = (1 << 5),
BotCallbackSent = (1 << 6),
NewMaybeAdded = (1 << 7),
LastUsedBit = (1 << 6),
LastUsedBit = (1 << 7),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -676,6 +676,9 @@ void Histories::checkPostponed(not_null<History*> history, int id) {
}
void Histories::cancelRequest(int id) {
if (!id) {
return;
}
const auto history = _historyByRequest.take(id);
if (!history) {
return;

View File

@ -0,0 +1,371 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_replies_list.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
#include "data/data_histories.h"
#include "data/data_session.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_messages.h"
#include "apiwrap.h"
namespace Data {
namespace {
constexpr auto kMessagesPerPage = 4; // #TODO replies
} // namespace
struct RepliesList::Viewer {
MessagesSlice slice;
MsgId around = 0;
int limitBefore = 0;
int limitAfter = 0;
};
RepliesList::RepliesList(not_null<History*> history, MsgId rootId)
: _history(history)
, _rootId(rootId) {
}
RepliesList::~RepliesList() {
histories().cancelRequest(base::take(_beforeId));
histories().cancelRequest(base::take(_afterId));
}
rpl::producer<MessagesSlice> RepliesList::source(
MessagePosition aroundId,
int limitBefore,
int limitAfter) {
const auto around = aroundId.fullId.msg;
return [=](auto consumer) {
auto lifetime = rpl::lifetime();
const auto viewer = lifetime.make_state<Viewer>();
const auto push = [=] {
if (buildFromData(viewer)) {
consumer.put_next_copy(viewer->slice);
}
};
viewer->around = around;
viewer->limitBefore = limitBefore;
viewer->limitAfter = limitAfter;
_history->session().changes().messageUpdates(
MessageUpdate::Flag::NewAdded
| MessageUpdate::Flag::NewMaybeAdded
| MessageUpdate::Flag::Destroyed
) | rpl::filter([=](const MessageUpdate &update) {
return applyUpdate(viewer, update);
}) | rpl::start_with_next([=] {
crl::on_main(this, push);
}, lifetime);
_partLoaded.events(
) | rpl::start_with_next([=] {
crl::on_main(this, push);
}, lifetime);
push();
return lifetime;
};
}
rpl::producer<int> RepliesList::fullCount() const {
return _fullCount.value() | rpl::filter_optional();
}
bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
if (_list.empty() && _skippedBefore == 0 && _skippedAfter == 0) {
viewer->slice.ids.clear();
viewer->slice.fullCount
= viewer->slice.skippedBefore
= viewer->slice.skippedAfter
= 0;
return true;
}
const auto around = viewer->around;
if (_list.empty()
|| (!around && _skippedAfter != 0)
|| (around > 0 && around < _list.back())
|| (around > _list.front())) {
loadAround(around);
return false;
}
const auto i = around
? ranges::lower_bound(_list, around, std::greater<>())
: end(_list);
const auto availableBefore = (i - begin(_list));
const auto availableAfter = (end(_list) - i);
const auto useBefore = std::min(availableBefore, viewer->limitBefore);
const auto useAfter = std::min(availableAfter, viewer->limitAfter + 1);
const auto slice = &viewer->slice;
if (_skippedBefore.has_value()) {
slice->skippedBefore
= (*_skippedBefore + (availableBefore - useBefore));
}
if (_skippedAfter.has_value()) {
slice->skippedAfter
= (*_skippedAfter + (availableAfter - useAfter));
}
const auto channelId = _history->channelId();
slice->ids.clear();
slice->ids.reserve(useBefore + useAfter);
for (auto j = i - useBefore, e = i + useAfter; j != e; ++j) {
slice->ids.emplace_back(channelId, *j);
}
ranges::reverse(slice->ids);
slice->fullCount = _fullCount.current();
if (_skippedBefore != 0 && useBefore < viewer->limitBefore) {
loadBefore();
}
if (_skippedAfter != 0 && useAfter < viewer->limitAfter + 1) {
loadAfter();
}
return true;
}
bool RepliesList::applyUpdate(
not_null<Viewer*> viewer,
const MessageUpdate &update) {
if (update.item->history() != _history
|| update.item->replyToTop() != _rootId) {
return false;
}
const auto id = update.item->id;
const auto i = ranges::lower_bound(_list, id, std::greater<>());
if (update.flags & MessageUpdate::Flag::Destroyed) {
if (i == end(_list) || *i != id) {
return false;
}
_list.erase(i);
if (_skippedBefore && _skippedAfter) {
_fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
} else if (const auto known = _fullCount.current()) {
if (*known > 0) {
_fullCount = (*known - 1);
}
}
} else if (_skippedAfter != 0) {
return false;
} else {
if (i != end(_list) && *i == id) {
return false;
}
_list.insert(i, id);
if (_skippedBefore && _skippedAfter) {
_fullCount = *_skippedBefore + _list.size() + *_skippedAfter;
} else if (const auto known = _fullCount.current()) {
_fullCount = *known + 1;
}
}
return true;
}
Histories &RepliesList::histories() {
return _history->owner().histories();
}
void RepliesList::loadAround(MsgId id) {
if (_loadingAround && *_loadingAround == id) {
return;
}
histories().cancelRequest(base::take(_beforeId));
histories().cancelRequest(base::take(_afterId));
const auto send = [=](Fn<void()> finish) {
return _history->session().api().request(MTPmessages_GetReplies(
_history->peer->input,
MTP_int(_rootId),
MTP_int(id),
MTP_int(id ? (-kMessagesPerPage / 2) : 0),
MTP_int(kMessagesPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
_beforeId = 0;
_loadingAround = std::nullopt;
finish();
if (!id) {
_skippedAfter = 0;
} else {
_skippedAfter = std::nullopt;
}
_skippedBefore = std::nullopt;
_list.clear();
if (processMessagesIsEmpty(result)) {
_fullCount = _skippedBefore = _skippedAfter = 0;
}
}).fail([=](const RPCError &error) {
_beforeId = 0;
_loadingAround = std::nullopt;
finish();
}).send();
};
_loadingAround = id;
_beforeId = histories().sendRequest(
_history,
Histories::RequestType::History,
send);
}
void RepliesList::loadBefore() {
Expects(!_list.empty());
if (_loadingAround) {
histories().cancelRequest(base::take(_beforeId));
} else if (_beforeId) {
return;
}
const auto last = _list.back();
const auto send = [=](Fn<void()> finish) {
return _history->session().api().request(MTPmessages_GetReplies(
_history->peer->input,
MTP_int(_rootId),
MTP_int(last),
MTP_int(0),
MTP_int(kMessagesPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
_beforeId = 0;
finish();
if (_list.empty()) {
return;
} else if (_list.back() != last) {
loadBefore();
} else if (processMessagesIsEmpty(result)) {
_skippedBefore = 0;
if (_skippedAfter == 0) {
_fullCount = _list.size();
}
}
}).fail([=](const RPCError &error) {
_beforeId = 0;
finish();
}).send();
};
_beforeId = histories().sendRequest(
_history,
Histories::RequestType::History,
send);
}
void RepliesList::loadAfter() {
Expects(!_list.empty());
if (_afterId) {
return;
}
const auto first = _list.front();
const auto send = [=](Fn<void()> finish) {
return _history->session().api().request(MTPmessages_GetReplies(
_history->peer->input,
MTP_int(_rootId),
MTP_int(first + 1),
MTP_int(-kMessagesPerPage),
MTP_int(kMessagesPerPage),
MTP_int(0),
MTP_int(0),
MTP_int(0)
)).done([=](const MTPmessages_Messages &result) {
_afterId = 0;
finish();
if (_list.empty()) {
return;
} else if (_list.front() != first) {
loadAfter();
} else if (processMessagesIsEmpty(result)) {
_skippedAfter = 0;
if (_skippedBefore == 0) {
_fullCount = _list.size();
}
}
}).fail([=](const RPCError &error) {
_afterId = 0;
finish();
}).send();
};
_afterId = histories().sendRequest(
_history,
Histories::RequestType::History,
send);
}
bool RepliesList::processMessagesIsEmpty(const MTPmessages_Messages &result) {
const auto guard = gsl::finally([&] { _partLoaded.fire({}); });
_fullCount = result.match([&](const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! "
"(HistoryWidget::messagesReceived)"));
return 0;
}, [&](const MTPDmessages_messages &data) {
return int(data.vmessages().v.size());
}, [&](const MTPDmessages_messagesSlice &data) {
return data.vcount().v;
}, [&](const MTPDmessages_channelMessages &data) {
if (_history->peer->isChannel()) {
_history->peer->asChannel()->ptsReceived(data.vpts().v);
} else {
LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (HistoryWidget::messagesReceived)"));
}
return data.vcount().v;
});
auto &owner = _history->owner();
const auto list = result.match([&](
const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! "
"(HistoryWidget::messagesReceived)"));
return QVector<MTPMessage>();
}, [&](const auto &data) {
owner.processUsers(data.vusers());
owner.processChats(data.vchats());
return data.vmessages().v;
});
if (list.isEmpty()) {
return true;
}
const auto id = IdFromMessage(list.front());
const auto toFront = !_list.empty() && (id > _list.front());
const auto clientFlags = MTPDmessage_ClientFlags();
const auto type = NewMessageType::Existing;
auto refreshed = std::vector<MsgId>();
if (toFront) {
refreshed.reserve(_list.size() + list.size());
}
for (const auto &message : list) {
if (const auto item = owner.addNewMessage(message, clientFlags, type)) {
if (item->replyToTop() == _rootId) {
if (toFront) {
refreshed.push_back(item->id);
} else {
_list.push_back(item->id);
}
}
}
}
if (toFront) {
refreshed.insert(refreshed.end(), _list.begin(), _list.end());
_list = std::move(refreshed);
}
return false;
}
} // namespace Data

View File

@ -0,0 +1,60 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/weak_ptr.h"
class History;
namespace Data {
class Histories;
struct MessagePosition;
struct MessagesSlice;
struct MessageUpdate;
class RepliesList final : public base::has_weak_ptr {
public:
RepliesList(not_null<History*> history, MsgId rootId);
~RepliesList();
[[nodiscard]] rpl::producer<MessagesSlice> source(
MessagePosition aroundId,
int limitBefore,
int limitAfter);
[[nodiscard]] rpl::producer<int> fullCount() const;
private:
struct Viewer;
[[nodiscard]] Histories &histories();
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
[[nodiscard]] bool applyUpdate(
not_null<Viewer*> viewer,
const MessageUpdate &update);
bool processMessagesIsEmpty(const MTPmessages_Messages &result);
void loadAround(MsgId id);
void loadBefore();
void loadAfter();
const not_null<History*> _history;
const MsgId _rootId = 0;
std::vector<MsgId> _list;
std::optional<int> _skippedBefore;
std::optional<int> _skippedAfter;
rpl::variable<std::optional<int>> _fullCount;
rpl::event_stream<> _partLoaded;
std::optional<MsgId> _loadingAround;
int _beforeId = 0;
int _afterId = 0;
};
} // namespace Data

View File

@ -1685,6 +1685,9 @@ bool Session::checkEntitiesAndViewsUpdate(const MTPDmessage &data) {
if (result) {
stickers().checkSavedGif(existing);
}
session().changes().messageUpdated(
existing,
Data::MessageUpdate::Flag::NewMaybeAdded);
return result;
}

View File

@ -1060,6 +1060,9 @@ void History::applyMessageChanges(
applyServiceChanges(item, data.c_messageService());
}
owner().stickers().checkSavedGif(item);
session().changes().messageUpdated(
item,
Data::MessageUpdate::Flag::NewAdded);
}
void History::applyServiceChanges(
@ -1247,12 +1250,6 @@ void History::applyServiceChanges(
});
}
} break;
case mtpc_messageActionPhoneCall: {
item->history()->session().changes().messageUpdated(
item,
Data::MessageUpdate::Flag::CallAdded);
} break;
}
}

View File

@ -159,6 +159,11 @@ historyViewsInSelectedIcon: icon {{ "history_views", msgInDateFgSelected }};
historyViewsOutIcon: icon {{ "history_views", historyOutIconFg }};
historyViewsOutSelectedIcon: icon {{ "history_views", historyOutIconFgSelected }};
historyViewsInvertedIcon: icon {{ "history_views", historySendingInvertedIconFg }};
historyRepliesInIcon: icon {{ "history_replies", msgInDateFg }};
historyRepliesInSelectedIcon: icon {{ "history_replies", msgInDateFgSelected }};
historyRepliesOutIcon: icon {{ "history_replies", historyOutIconFg }};
historyRepliesOutSelectedIcon: icon {{ "history_replies", historyOutIconFgSelected }};
historyRepliesInvertedIcon: icon {{ "history_replies", historySendingInvertedIconFg }};
historyComposeField: InputField(defaultInputField) {
font: msgFont;

View File

@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_service_message.h"
#include "history/view/history_view_cursor_state.h"
#include "history/view/history_view_context_menu.h"
#include "history/view/history_view_replies_section.h"
#include "ui/widgets/popup_menu.h"
#include "ui/image/image.h"
#include "ui/toast/toast.h"
@ -1540,6 +1541,17 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
_widget->replyToMessage(itemId);
});
}
if (IsServerMsgId(item->id) && item->repliesCount() > 0) {
_menu->addAction(tr::lng_replies_view(tr::now, lt_count, item->repliesCount()), [=] {
controller->showSection(
HistoryView::RepliesMemento(_history, itemId.msg));
});
} else if (const auto replyToTop = item->replyToTop()) {
_menu->addAction(tr::lng_replies_view_thread(tr::now), [=] {
controller->showSection(
HistoryView::RepliesMemento(_history, replyToTop));
});
}
if (item->allowsEdit(base::unixtime::now())) {
_menu->addAction(tr::lng_context_edit_msg(tr::now), [=] {
_widget->editMessage(itemId);

View File

@ -1343,14 +1343,25 @@ void Message::drawInfo(
}
if (auto views = item->Get<HistoryMessageViews>()) {
const auto showReplies = (views->views < 0) && (views->replies > 0);
auto icon = [&] {
if (item->id > 0) {
if (outbg) {
return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsOutSelectedIcon : st::historyViewsOutIcon));
return &(invertedsprites
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon)
: selected
? (showReplies ? st::historyRepliesOutSelectedIcon : st::historyViewsOutSelectedIcon)
: (showReplies ? st::historyRepliesOutIcon : st::historyViewsOutIcon));
}
return &(invertedsprites ? st::historyViewsInvertedIcon : (selected ? st::historyViewsInSelectedIcon : st::historyViewsInIcon));
return &(invertedsprites
? (showReplies ? st::historyRepliesInvertedIcon : st::historyViewsInvertedIcon)
: selected
? (showReplies ? st::historyRepliesInSelectedIcon : st::historyViewsInSelectedIcon)
: (showReplies ? st::historyRepliesInIcon : st::historyViewsInIcon));
}
return &(invertedsprites ? st::historyViewsSendingInvertedIcon : st::historyViewsSendingIcon);
return &(invertedsprites
? st::historyViewsSendingInvertedIcon
: st::historyViewsSendingIcon);
}();
if (item->id > 0) {
icon->paint(p, infoRight - infoW, infoBottom + st::historyViewsTop, width);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,263 @@
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "window/section_widget.h"
#include "window/section_memento.h"
#include "history/view/history_view_list_widget.h"
#include "data/data_messages.h"
class History;
enum class CompressConfirm;
enum class SendMediaType;
struct SendingAlbum;
namespace SendMenu {
enum class Type;
} // namespace SendMenu
namespace Api {
struct SendOptions;
} // namespace Api
namespace Storage {
struct PreparedList;
} // namespace Storage
namespace Ui {
class ScrollArea;
class PlainShadow;
class FlatButton;
class HistoryDownButton;
} // namespace Ui
namespace Profile {
class BackButton;
} // namespace Profile
namespace InlineBots {
class Result;
} // namespace InlineBots
namespace Data {
class RepliesList;
} // namespace Data
namespace HistoryView {
class Element;
class TopBarWidget;
class RepliesMemento;
class ComposeControls;
class RepliesWidget final
: public Window::SectionWidget
, private ListDelegate {
public:
RepliesWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history,
MsgId rootId);
~RepliesWidget();
[[nodiscard]] not_null<History*> history() const;
Dialogs::RowDescriptor activeChat() const override;
bool hasTopBarShadow() const override {
return true;
}
QPixmap grabForShowAnimation(
const Window::SectionSlideParams &params) override;
bool showInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) override;
std::unique_ptr<Window::SectionMemento> createMemento() override;
void setInternalState(
const QRect &geometry,
not_null<RepliesMemento*> memento);
// Tabbed selector management.
bool pushTabbedSelectorToThirdSection(
not_null<PeerData*> peer,
const Window::SectionShow &params) override;
bool returnTabbedSelector() override;
// Float player interface.
bool floatPlayerHandleWheelEvent(QEvent *e) override;
QRect floatPlayerAvailableRect() override;
// ListDelegate interface.
Context listContext() override;
void listScrollTo(int top) override;
void listCancelRequest() override;
void listDeleteRequest() override;
rpl::producer<Data::MessagesSlice> listSource(
Data::MessagePosition aroundId,
int limitBefore,
int limitAfter) override;
bool listAllowsMultiSelect() override;
bool listIsItemGoodForSelection(not_null<HistoryItem*> item) override;
bool listIsLessInOrder(
not_null<HistoryItem*> first,
not_null<HistoryItem*> second) override;
void listSelectionChanged(SelectedItems &&items) override;
void listVisibleItemsChanged(HistoryItemsList &&items) override;
std::optional<int> listUnreadBarView(
const std::vector<not_null<Element*>> &elements) override;
void listContentRefreshed() override;
ClickHandlerPtr listDateLink(not_null<Element*> view) override;
protected:
void resizeEvent(QResizeEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void showAnimatedHook(
const Window::SectionSlideParams &params) override;
void showFinishedHook() override;
void doSetInnerFocus() override;
private:
void onScroll();
void updateInnerVisibleArea();
void updateControlsGeometry();
void updateAdaptiveLayout();
void saveState(not_null<RepliesMemento*> memento);
void restoreState(not_null<RepliesMemento*> memento);
void showAtPosition(Data::MessagePosition position);
bool showAtPositionNow(Data::MessagePosition position);
void setupComposeControls();
void setupDragArea();
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void confirmSendNowSelected();
void confirmDeleteSelected();
void clearSelected();
void send();
void send(Api::SendOptions options);
void edit(
not_null<HistoryItem*> item,
Api::SendOptions options,
mtpRequestId *const saveEditMsgRequestId);
void highlightSingleNewMessage(const Data::MessagesSlice &slice);
void chooseAttach();
[[nodiscard]] SendMenu::Type sendMenuType() const;
void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles(
QImage &&image,
QByteArray &&content,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
Storage::PreparedList &&list,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool confirmSendingFiles(
not_null<const QMimeData*> data,
CompressConfirm compressed,
const QString &insertTextOnCancel = QString());
bool showSendingFilesError(const Storage::PreparedList &list) const;
void uploadFilesAfterConfirmation(
Storage::PreparedList &&list,
SendMediaType type,
TextWithTags &&caption,
MsgId replyTo,
Api::SendOptions options,
std::shared_ptr<SendingAlbum> album);
void sendExistingDocument(not_null<DocumentData*> document);
bool sendExistingDocument(
not_null<DocumentData*> document,
Api::SendOptions options);
void sendExistingPhoto(not_null<PhotoData*> photo);
bool sendExistingPhoto(
not_null<PhotoData*> photo,
Api::SendOptions options);
void sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot);
void sendInlineResult(
not_null<InlineBots::Result*> result,
not_null<UserData*> bot,
Api::SendOptions options);
const not_null<History*> _history;
const MsgId _rootId = 0;
std::shared_ptr<Data::RepliesList> _replies;
object_ptr<Ui::ScrollArea> _scroll;
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;
object_ptr<Ui::PlainShadow> _topBarShadow;
std::unique_ptr<ComposeControls> _composeControls;
bool _skipScrollEvent = false;
FullMsgId _highlightMessageId;
std::optional<Data::MessagePosition> _nextAnimatedScrollPosition;
int _nextAnimatedScrollDelta = 0;
Ui::Animations::Simple _scrollDownShown;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false;
};
class RepliesMemento : public Window::SectionMemento {
public:
RepliesMemento(not_null<History*> history, MsgId rootId)
: _history(history)
, _rootId(rootId) {
}
object_ptr<Window::SectionWidget> createWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
Window::Column column,
const QRect &geometry) override;
[[nodiscard]] not_null<History*> getHistory() const {
return _history;
}
[[nodiscard]] MsgId getRootId() const {
return _rootId;
}
void setReplies(std::shared_ptr<Data::RepliesList> replies) {
_replies = std::move(replies);
}
[[nodiscard]] std::shared_ptr<Data::RepliesList> getReplies() const {
return _replies;
}
[[nodiscard]] not_null<ListMemento*> list() {
return &_list;
}
private:
const not_null<History*> _history;
const MsgId _rootId = 0;
ListMemento _list;
std::shared_ptr<Data::RepliesList> _replies;
};
} // namespace HistoryView

View File

@ -240,4 +240,4 @@ private:
};
} // namespace HistoryScheduled
} // namespace HistoryView

View File

@ -330,9 +330,12 @@ void TopBarWidget::paintTopBar(Painter &p) {
const auto folder = _activeChat.folder();
if (folder
|| history->peer->isSelf()
|| (_section == Section::Scheduled)) {
|| (_section == Section::Scheduled)
|| !_customTitleText.isEmpty()) {
// #TODO feed name emoji.
auto text = (_section == Section::Scheduled)
auto text = !_customTitleText.isEmpty()
? _customTitleText
: (_section == Section::Scheduled)
? ((history && history->peer->isSelf())
? tr::lng_reminder_messages(tr::now)
: tr::lng_scheduled_messages(tr::now))
@ -497,6 +500,13 @@ void TopBarWidget::setActiveChat(Dialogs::Key chat, Section section) {
refreshUnreadBadge();
}
void TopBarWidget::setCustomTitle(const QString &title) {
if (_customTitleText != title) {
_customTitleText = title;
update();
}
}
void TopBarWidget::refreshInfoButton() {
if (const auto peer = _activeChat.peer()) {
auto info = object_ptr<Ui::UserpicButton>(

View File

@ -44,6 +44,7 @@ public:
enum class Section {
History,
Scheduled,
Replies,
};
TopBarWidget(
@ -62,6 +63,7 @@ public:
void setAnimatingMode(bool enabled);
void setActiveChat(Dialogs::Key chat, Section section);
void setCustomTitle(const QString &title);
rpl::producer<> forwardSelectionRequest() const {
return _forwardSelection.events();
@ -125,6 +127,7 @@ private:
const not_null<Window::SessionController*> _controller;
Dialogs::Key _activeChat;
Section _section = Section::History;
QString _customTitleText;
int _selectedCount = 0;
bool _canDelete = false;

View File

@ -1137,7 +1137,7 @@ bool Gif::needsBubble() const {
}
const auto item = _parent->data();
return item->viaBot()
|| item->Has<HistoryMessageReply>()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName();
return false;

View File

@ -320,7 +320,7 @@ bool Location::needsBubble() const {
}
const auto item = _parent->data();
return item->viaBot()
|| item->Has<HistoryMessageReply>()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName();
return false;

View File

@ -454,7 +454,7 @@ bool GroupedMedia::computeNeedBubble() const {
}
if (const auto item = _parent->data()) {
if (item->viaBot()
|| item->Has<HistoryMessageReply>()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName()
) {

View File

@ -802,7 +802,7 @@ bool Photo::needsBubble() const {
const auto item = _parent->data();
if (item->toHistoryMessage()) {
return item->viaBot()
|| item->Has<HistoryMessageReply>()
|| _parent->displayedReply()
|| _parent->displayForwardedFrom()
|| _parent->displayFromName();
}