Add read status tracking to comments.

This commit is contained in:
John Preston 2020-09-08 14:19:44 +03:00
parent 78d83a2c69
commit b8424b1d89
11 changed files with 182 additions and 11 deletions

View file

@ -165,7 +165,14 @@ bool RepliesList::buildFromData(not_null<Viewer*> viewer) {
= 0;
return true;
}
const auto around = viewer->around;
const auto around = [&] {
if (viewer->around != ShowAtUnreadMsgId) {
return viewer->around;
} else if (const auto item = lookupRoot()) {
return item->repliesReadTill();
}
return viewer->around;
}();
if (_list.empty()
|| (!around && _skippedAfter != 0)
|| (around > _list.front() && _skippedAfter != 0)
@ -251,6 +258,10 @@ Histories &RepliesList::histories() {
return _history->owner().histories();
}
HistoryItem *RepliesList::lookupRoot() {
return _history->owner().message(_history->channelId(), _rootId);
}
void RepliesList::loadAround(MsgId id) {
if (_loadingAround && *_loadingAround == id) {
return;

View file

@ -33,6 +33,7 @@ public:
private:
struct Viewer;
HistoryItem *lookupRoot();
[[nodiscard]] Histories &histories();
[[nodiscard]] rpl::producer<MessagesSlice> sourceFromServer(

View file

@ -264,6 +264,11 @@ public:
}
virtual void changeRepliesCount(int delta, PeerId replier) {
}
virtual void setRepliesReadTill(MsgId readTillId) {
}
[[nodiscard]] virtual MsgId repliesReadTill() const {
return MsgId(0);
}
virtual void setReplyToTop(MsgId replyToTop) {
}
virtual void setRealId(MsgId newId);

View file

@ -46,6 +46,7 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
Part replies;
ChannelId commentsChannelId = 0;
MsgId commentsRootId = 0;
MsgId repliesReadTillId = 0;
};
struct HistoryMessageSigned : public RuntimeComponent<HistoryMessageSigned, HistoryItem> {

View file

@ -1561,6 +1561,22 @@ void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
refreshRepliesText(views);
}
void HistoryMessage::setRepliesReadTill(MsgId readTillId) {
auto views = Get<HistoryMessageViews>();
if (!views) {
AddComponents(HistoryMessageViews::Bit());
views = Get<HistoryMessageViews>();
}
views->repliesReadTillId = std::max(readTillId, 1);
}
MsgId HistoryMessage::repliesReadTill() const {
if (const auto views = Get<HistoryMessageViews>()) {
return views->repliesReadTillId;
}
return 0;
}
void HistoryMessage::setReplyToTop(MsgId replyToTop) {
const auto reply = Get<HistoryMessageReply>();
if (!reply

View file

@ -136,6 +136,8 @@ public:
void setForwardsCount(int count) override;
void setReplies(const MTPMessageReplies &data) override;
void changeRepliesCount(int delta, PeerId replier) override;
void setRepliesReadTill(MsgId readTillId) override;
MsgId repliesReadTill() const override;
void setReplyToTop(MsgId replyToTop) override;
void setRealId(MsgId newId) override;
void incrementReplyToTopCounter() override;

View file

@ -1265,8 +1265,8 @@ void ListWidget::restoreState(not_null<ListMemento*> memento) {
_aroundIndex = -1;
if (const auto limit = memento->idsLimit()) {
_idsLimit = limit;
_scrollTopState = memento->scrollTopState();
}
_scrollTopState = memento->scrollTopState();
refreshViewer();
}
@ -2663,6 +2663,10 @@ void ListWidget::replyToMessageRequestNotify(FullMsgId item) {
_requestedToReplyToMessage.fire(std::move(item));
}
rpl::producer<FullMsgId> ListWidget::readMessageRequested() const {
return _requestedToReadMessage.events();
}
ListWidget::~ListWidget() = default;
} // namespace HistoryView

View file

@ -188,10 +188,11 @@ public:
QPoint tooltipPos() const override;
bool tooltipWindowActive() const override;
rpl::producer<FullMsgId> editMessageRequested() const;
[[nodiscard]] rpl::producer<FullMsgId> editMessageRequested() const;
void editMessageRequestNotify(FullMsgId item);
rpl::producer<FullMsgId> replyToMessageRequested() const;
[[nodiscard]] rpl::producer<FullMsgId> replyToMessageRequested() const;
void replyToMessageRequestNotify(FullMsgId item);
[[nodiscard]] rpl::producer<FullMsgId> readMessageRequested() const;
// ElementDelegate interface.
Context elementContext() override;
@ -529,6 +530,7 @@ private:
rpl::event_stream<FullMsgId> _requestedToEditMessage;
rpl::event_stream<FullMsgId> _requestedToReplyToMessage;
rpl::event_stream<FullMsgId> _requestedToReadMessage;
rpl::lifetime _viewerLifetime;

View file

@ -59,6 +59,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryView {
namespace {
constexpr auto kReadRequestTimeout = 3 * crl::time(1000);
void ShowErrorToast(const QString &text) {
Ui::Toast::Show(Ui::Toast::Config{
.text = { text },
@ -80,6 +82,16 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
} // namespace
RepliesMemento::RepliesMemento(not_null<HistoryItem*> commentsItem)
: RepliesMemento(commentsItem->history(), commentsItem->id) {
if (commentsItem->repliesReadTill() == MsgId(1)) {
_list.setAroundPosition(Data::MinMessagePosition);
_list.setScrollTopState(ListMemento::ScrollTopState{
Data::MinMessagePosition
});
}
}
object_ptr<Window::SectionWidget> RepliesMemento::createWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
@ -106,6 +118,7 @@ RepliesWidget::RepliesWidget(
, _history(history)
, _rootId(rootId)
, _root(lookupRoot())
, _commentsRoot(lookupCommentsRoot())
, _areComments(computeAreComments())
, _scroll(this, st::historyScroll, false)
, _topBar(this, controller)
@ -115,7 +128,8 @@ RepliesWidget::RepliesWidget(
controller,
ComposeControls::Mode::Normal))
, _rootShadow(this)
, _scrollDown(_scroll, st::historyToDown) {
, _scrollDown(_scroll, st::historyToDown)
, _readRequestTimer([=] { sendReadTillRequest(); }) {
setupRoot();
_rootHeight = st::msgReplyPadding.top()
@ -178,9 +192,13 @@ RepliesWidget::RepliesWidget(
_history->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed
) | rpl::filter([=](const Data::MessageUpdate &update) {
return (update.item == _replyReturn);
}) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.item == _root) {
_root = nullptr;
}
if (update.item == _commentsRoot) {
_commentsRoot = nullptr;
}
while (update.item == _replyReturn) {
calculateNextReplyReturn();
}
@ -190,10 +208,33 @@ RepliesWidget::RepliesWidget(
setupComposeControls();
}
RepliesWidget::~RepliesWidget() = default;
RepliesWidget::~RepliesWidget() {
if (_readRequestTimer.isActive()) {
sendReadTillRequest();
}
}
void RepliesWidget::sendReadTillRequest() {
if (!_commentsRoot || !_root) {
return;
}
if (_readRequestTimer.isActive()) {
_readRequestTimer.cancel();
}
const auto api = &_history->session().api();
api->request(base::take(_readRequestId)).cancel();
_readRequestId = api->request(MTPmessages_ReadDiscussion(
_commentsRoot->history()->peer->input,
MTP_int(_commentsRoot->id),
MTP_int(_root->repliesReadTill())
)).done([=](const MTPBool &) {
}).send();
}
void RepliesWidget::setupRoot() {
if (_root) {
setupCommentsRoot();
refreshRootView();
} else {
const auto channel = _history->peer->asChannel();
@ -201,6 +242,7 @@ void RepliesWidget::setupRoot() {
_root = lookupRoot();
if (_root) {
_areComments = computeAreComments();
setupCommentsRoot();
}
refreshRootView();
});
@ -208,6 +250,30 @@ void RepliesWidget::setupRoot() {
}
}
void RepliesWidget::setupCommentsRoot() {
Expects(_root != nullptr);
const auto postChannel = _root->discussionPostOriginalSender();
if (!postChannel) {
return;
} else if (_commentsRoot) {
sendReadTillRequest();
} else {
const auto forwarded = _root->Get<HistoryMessageForwarded>();
const auto messageId = forwarded->savedFromMsgId;
const auto done = crl::guard(this, [=](ChannelData*, MsgId) {
_commentsRoot = lookupCommentsRoot();
if (_commentsRoot) {
sendReadTillRequest();
}
});
_history->session().api().requestMessageData(
postChannel,
messageId,
done);
}
}
void RepliesWidget::refreshRootView() {
const auto sender = (_root && _root->discussionPostOriginalSender())
? _root->discussionPostOriginalSender()
@ -240,6 +306,17 @@ HistoryItem *RepliesWidget::lookupRoot() const {
return _history->owner().message(_history->channelId(), _rootId);
}
HistoryItem *RepliesWidget::lookupCommentsRoot() const {
if (!computeAreComments()) {
return nullptr;
}
const auto forwarded = _root->Get<HistoryMessageForwarded>();
Assert(forwarded != nullptr);
return _history->owner().message(
forwarded->savedFromPeer->asChannel(),
forwarded->savedFromMsgId);
}
bool RepliesWidget::computeAreComments() const {
return _root && _root->isDiscussionPost();
}
@ -1364,11 +1441,49 @@ void RepliesWidget::listSelectionChanged(SelectedItems &&items) {
_topBar->showSelected(state);
}
void RepliesWidget::readTill(MsgId tillId) {
if (!_root) {
return;
}
const auto now = _root->repliesReadTill();
if (now < tillId) {
_root->setRepliesReadTill(tillId);
if (!_readRequestTimer.isActive()) {
_readRequestTimer.callOnce(kReadRequestTimeout);
}
}
}
void RepliesWidget::listVisibleItemsChanged(HistoryItemsList &&items) {
const auto reversed = ranges::view::reverse(items);
const auto good = ranges::find_if(reversed, [](auto item) {
return IsServerMsgId(item->id);
});
if (good != end(reversed)) {
readTill((*good)->id);
}
}
std::optional<int> RepliesWidget::listUnreadBarView(
const std::vector<not_null<Element*>> &elements) {
if (!_root) {
return std::nullopt;
}
const auto till = _root->repliesReadTill();
if (till < 2) {
return std::nullopt;
}
for (auto i = 0, count = int(elements.size()); i != count; ++i) {
const auto item = elements[i]->data();
if (item->id > till) {
if (item->out()) {
_root->setRepliesReadTill(item->id);
_readRequestTimer.callOnce(kReadRequestTimeout);
} else {
return i;
}
}
}
return std::nullopt;
}

View file

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/section_memento.h"
#include "history/view/history_view_list_widget.h"
#include "data/data_messages.h"
#include "base/timer.h"
class History;
enum class CompressConfirm;
@ -148,8 +149,11 @@ private:
void setupComposeControls();
void setupRoot();
void setupCommentsRoot();
void refreshRootView();
void setupDragArea();
void sendReadTillRequest();
void readTill(MsgId id);
void setupScrollDownButton();
void scrollDownClicked();
@ -172,6 +176,7 @@ private:
[[nodiscard]] SendMenu::Type sendMenuType() const;
[[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] HistoryItem *lookupCommentsRoot() const;
[[nodiscard]] bool computeAreComments() const;
void pushReplyReturn(not_null<HistoryItem*> item);
@ -224,6 +229,7 @@ private:
const not_null<History*> _history;
const MsgId _rootId = 0;
HistoryItem *_root = nullptr;
HistoryItem *_commentsRoot = nullptr;
std::shared_ptr<Data::RepliesList> _replies;
rpl::variable<bool> _areComments = false;
object_ptr<Ui::ScrollArea> _scroll;
@ -248,14 +254,19 @@ private:
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false;
base::Timer _readRequestTimer;
mtpRequestId _readRequestId = 0;
};
class RepliesMemento : public Window::SectionMemento {
public:
RepliesMemento(not_null<History*> history, MsgId rootId)
: _history(history)
, _rootId(rootId) {
}
explicit RepliesMemento(not_null<HistoryItem*> commentsItem);
object_ptr<Window::SectionWidget> createWidget(
QWidget *parent,

View file

@ -110,7 +110,7 @@ void SessionNavigation::showRepliesForMessage(
} else if (const auto id = item->commentsItemId()) {
if (const auto item = _session->data().message(id)) {
showSection(
HistoryView::RepliesMemento(item->history(), item->id));
HistoryView::RepliesMemento(item));
return;
}
}
@ -148,8 +148,11 @@ void SessionNavigation::showRepliesForMessage(
if (post) {
post->setCommentsItemId(item->fullId());
}
if (const auto readTill = data.vread_max_id()) {
item->setRepliesReadTill(readTill->v);
}
showSection(
HistoryView::RepliesMemento(item->history(), item->id));
HistoryView::RepliesMemento(item));
}
});
}).fail([=](const RPCError &error) {