Track and display unread count in discussions.

This commit is contained in:
John Preston 2021-08-30 18:37:09 +03:00
parent 85e4c8527b
commit c39024c7fd
12 changed files with 271 additions and 45 deletions

View file

@ -2094,17 +2094,18 @@ void Updates::feedUpdate(const MTPUpdate &update) {
const auto msgId = d.vtop_msg_id().v; const auto msgId = d.vtop_msg_id().v;
const auto readTillId = d.vread_max_id().v; const auto readTillId = d.vread_max_id().v;
const auto item = session().data().message(channelId, msgId); const auto item = session().data().message(channelId, msgId);
const auto unreadCount = std::nullopt;
if (item) { if (item) {
item->setRepliesInboxReadTill(readTillId); item->setRepliesInboxReadTill(readTillId, unreadCount);
if (const auto post = item->lookupDiscussionPostOriginal()) { if (const auto post = item->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(readTillId); post->setRepliesInboxReadTill(readTillId, unreadCount);
} }
} }
if (const auto broadcastId = d.vbroadcast_id()) { if (const auto broadcastId = d.vbroadcast_id()) {
if (const auto post = session().data().message( if (const auto post = session().data().message(
broadcastId->v, broadcastId->v,
d.vbroadcast_post()->v)) { d.vbroadcast_post()->v)) {
post->setRepliesInboxReadTill(readTillId); post->setRepliesInboxReadTill(readTillId, unreadCount);
} }
} }
} break; } break;

View file

@ -134,16 +134,17 @@ struct MessageUpdate {
enum class Flag : uint32 { enum class Flag : uint32 {
None = 0, None = 0,
Edited = (1U << 0), Edited = (1U << 0),
Destroyed = (1U << 1), Destroyed = (1U << 1),
DialogRowRepaint = (1U << 2), DialogRowRepaint = (1U << 2),
DialogRowRefresh = (1U << 3), DialogRowRefresh = (1U << 3),
NewAdded = (1U << 4), NewAdded = (1U << 4),
ReplyMarkup = (1U << 5), ReplyMarkup = (1U << 5),
BotCallbackSent = (1U << 6), BotCallbackSent = (1U << 6),
NewMaybeAdded = (1U << 7), NewMaybeAdded = (1U << 7),
RepliesUnreadCount = (1U << 8),
LastUsedBit = (1U << 7), LastUsedBit = (1U << 7),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; } friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -101,6 +101,15 @@ rpl::producer<MessagesSlice> RepliesList::source(
_partLoaded.events( _partLoaded.events(
) | rpl::start_with_next(pushDelayed, lifetime); ) | rpl::start_with_next(pushDelayed, lifetime);
_history->session().data().channelDifferenceTooLong(
) | rpl::filter([=](not_null<ChannelData*> channel) {
if (_history->peer != channel || !_skippedAfter.has_value()) {
return false;
}
_skippedAfter = std::nullopt;
return true;
}) | rpl::start_with_next(pushDelayed, lifetime);
push(); push();
return lifetime; return lifetime;
}; };
@ -169,6 +178,64 @@ rpl::producer<int> RepliesList::fullCount() const {
return _fullCount.value() | rpl::filter_optional(); return _fullCount.value() | rpl::filter_optional();
} }
std::optional<int> RepliesList::fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> wasUnreadCountAfter) const {
Expects(readTillId >= wasReadTillId);
readTillId = std::max(readTillId, _rootId);
wasReadTillId = std::max(wasReadTillId, _rootId);
const auto backLoaded = (_skippedBefore == 0);
const auto frontLoaded = (_skippedAfter == 0);
const auto fullLoaded = backLoaded && frontLoaded;
const auto allUnread = (readTillId == _rootId)
|| (fullLoaded && _list.empty());
const auto countIncoming = [&](auto from, auto till) {
auto &owner = _history->owner();
const auto channelId = _history->channelId();
auto count = 0;
for (auto i = from; i != till; ++i) {
if (!owner.message(channelId, *i)->out()) {
++count;
}
}
return count;
};
if (allUnread && fullLoaded) {
// Should not happen too often unless the list is empty.
return countIncoming(begin(_list), end(_list));
} else if (frontLoaded && !_list.empty() && readTillId >= _list.front()) {
// Always "count by local data" if read till the end.
return 0;
} else if (wasReadTillId == readTillId) {
// Otherwise don't recount the same value over and over.
return wasUnreadCountAfter;
} else if (frontLoaded && !_list.empty() && readTillId >= _list.back()) {
// And count by local data if it is available and read-till changed.
return countIncoming(
begin(_list),
ranges::lower_bound(_list, readTillId, std::greater<>()));
} else if (_list.empty()) {
return std::nullopt;
} else if (wasUnreadCountAfter.has_value()
&& (frontLoaded || readTillId <= _list.front())
&& (backLoaded || wasReadTillId >= _list.back())) {
// Count how many were read since previous value.
const auto from = ranges::lower_bound(
_list,
readTillId,
std::greater<>());
const auto till = ranges::lower_bound(
from,
end(_list),
wasReadTillId,
std::greater<>());
return std::max(*wasUnreadCountAfter - countIncoming(from, till), 0);
}
return std::nullopt;
}
void RepliesList::injectRootMessageAndReverse(not_null<Viewer*> viewer) { void RepliesList::injectRootMessageAndReverse(not_null<Viewer*> viewer) {
injectRootMessage(viewer); injectRootMessage(viewer);
ranges::reverse(viewer->slice.ids); ranges::reverse(viewer->slice.ids);

View file

@ -31,6 +31,11 @@ public:
[[nodiscard]] rpl::producer<int> fullCount() const; [[nodiscard]] rpl::producer<int> fullCount() const;
[[nodiscard]] std::optional<int> fullUnreadCountAfter(
MsgId readTillId,
MsgId wasReadTillId,
std::optional<int> wasUnreadCountAfter) const;
private: private:
struct Viewer; struct Viewer;

View file

@ -223,7 +223,9 @@ public:
[[nodiscard]] virtual MsgId repliesInboxReadTill() const { [[nodiscard]] virtual MsgId repliesInboxReadTill() const {
return MsgId(0); return MsgId(0);
} }
virtual void setRepliesInboxReadTill(MsgId readTillId) { virtual void setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
} }
[[nodiscard]] virtual MsgId computeRepliesInboxReadTillFull() const { [[nodiscard]] virtual MsgId computeRepliesInboxReadTillFull() const {
return MsgId(0); return MsgId(0);
@ -316,7 +318,10 @@ public:
} }
virtual void clearReplies() { virtual void clearReplies() {
} }
virtual void changeRepliesCount(int delta, PeerId replier) { virtual void changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) {
} }
virtual void setReplyToTop(MsgId replyToTop) { virtual void setReplyToTop(MsgId replyToTop) {
} }

View file

@ -49,6 +49,7 @@ struct HistoryMessageViews : public RuntimeComponent<HistoryMessageViews, Histor
MsgId repliesInboxReadTillId = 0; MsgId repliesInboxReadTillId = 0;
MsgId repliesOutboxReadTillId = 0; MsgId repliesOutboxReadTillId = 0;
MsgId repliesMaxId = 0; MsgId repliesMaxId = 0;
int repliesUnreadCount = -1; // unknown
ChannelId commentsMegagroupId = 0; ChannelId commentsMegagroupId = 0;
MsgId commentsRootId = 0; MsgId commentsRootId = 0;
}; };

View file

@ -816,16 +816,30 @@ MsgId HistoryMessage::repliesInboxReadTill() const {
return 0; return 0;
} }
void HistoryMessage::setRepliesInboxReadTill(MsgId readTillId) { void HistoryMessage::setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) {
if (const auto views = Get<HistoryMessageViews>()) { if (const auto views = Get<HistoryMessageViews>()) {
const auto newReadTillId = std::max(readTillId, 1); const auto newReadTillId = std::max(readTillId, 1);
if (newReadTillId > views->repliesInboxReadTillId) { const auto ignore = (newReadTillId < views->repliesInboxReadTillId);
if (ignore) {
return;
}
const auto changed = (newReadTillId > views->repliesInboxReadTillId);
if (changed) {
const auto wasUnread = repliesAreComments() && areRepliesUnread(); const auto wasUnread = repliesAreComments() && areRepliesUnread();
views->repliesInboxReadTillId = newReadTillId; views->repliesInboxReadTillId = newReadTillId;
if (wasUnread && !areRepliesUnread()) { if (wasUnread && !areRepliesUnread()) {
history()->owner().requestItemRepaint(this); history()->owner().requestItemRepaint(this);
} }
} }
const auto wasUnreadCount = (views->repliesUnreadCount >= 0)
? std::make_optional(views->repliesUnreadCount)
: std::nullopt;
if (unreadCount != wasUnreadCount
&& (changed || unreadCount.has_value())) {
setUnreadRepliesCount(views, unreadCount.value_or(-1));
}
} }
} }
@ -1808,10 +1822,27 @@ void HistoryMessage::refreshRepliesText(
} }
} }
void HistoryMessage::changeRepliesCount(int delta, PeerId replier) { void HistoryMessage::changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) {
const auto views = Get<HistoryMessageViews>(); const auto views = Get<HistoryMessageViews>();
const auto limit = HistoryMessageViews::kMaxRecentRepliers; const auto limit = HistoryMessageViews::kMaxRecentRepliers;
if (!views || views->replies.count < 0) { if (!views) {
return;
}
// Update unread count.
if (!unread) {
setUnreadRepliesCount(views, -1);
} else if (views->repliesUnreadCount >= 0 && *unread) {
setUnreadRepliesCount(
views,
std::max(views->repliesUnreadCount + delta, 0));
}
// Update full count.
if (views->replies.count < 0) {
return; return;
} }
views->replies.count = std::max(views->replies.count + delta, 0); views->replies.count = std::max(views->replies.count + delta, 0);
@ -1830,6 +1861,19 @@ void HistoryMessage::changeRepliesCount(int delta, PeerId replier) {
refreshRepliesText(views); refreshRepliesText(views);
} }
void HistoryMessage::setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count) {
// Track unread count in discussion forwards, not in the channel posts.
if (views->repliesUnreadCount == count || views->commentsMegagroupId) {
return;
}
views->repliesUnreadCount = count;
history()->session().changes().messageUpdated(
this,
Data::MessageUpdate::Flag::RepliesUnreadCount);
}
void HistoryMessage::setReplyToTop(MsgId replyToTop) { void HistoryMessage::setReplyToTop(MsgId replyToTop) {
const auto reply = Get<HistoryMessageReply>(); const auto reply = Get<HistoryMessageReply>();
if (!reply if (!reply
@ -1877,19 +1921,23 @@ void HistoryMessage::changeReplyToTopCounter(
if (!top) { if (!top) {
return; return;
} }
const auto changeFor = [&](not_null<HistoryItem*> item) { auto unread = out() ? std::make_optional(false) : std::nullopt;
if (const auto from = displayFrom()) {
item->changeRepliesCount(delta, from->id);
return;
}
item->changeRepliesCount(delta, PeerId());
};
if (const auto views = top->Get<HistoryMessageViews>()) { if (const auto views = top->Get<HistoryMessageViews>()) {
if (views->commentsMegagroupId) { if (views->commentsMegagroupId) {
// This is a post in channel, we don't track its replies. // This is a post in channel, we don't track its replies.
return; return;
} }
if (views->repliesInboxReadTillId > 0) {
unread = !out() && (id > views->repliesInboxReadTillId);
}
} }
const auto changeFor = [&](not_null<HistoryItem*> item) {
if (const auto from = displayFrom()) {
item->changeRepliesCount(delta, from->id, unread);
} else {
item->changeRepliesCount(delta, PeerId(), unread);
}
};
changeFor(top); changeFor(top);
if (const auto original = top->lookupDiscussionPostOriginal()) { if (const auto original = top->lookupDiscussionPostOriginal()) {
changeFor(original); changeFor(original);

View file

@ -133,7 +133,10 @@ public:
void setForwardsCount(int count) override; void setForwardsCount(int count) override;
void setReplies(const MTPMessageReplies &data) override; void setReplies(const MTPMessageReplies &data) override;
void clearReplies() override; void clearReplies() override;
void changeRepliesCount(int delta, PeerId replier) override; void changeRepliesCount(
int delta,
PeerId replier,
std::optional<bool> unread) override;
void setReplyToTop(MsgId replyToTop) override; void setReplyToTop(MsgId replyToTop) override;
void setPostAuthor(const QString &author) override; void setPostAuthor(const QString &author) override;
void setRealId(MsgId newId) override; void setRealId(MsgId newId) override;
@ -181,7 +184,9 @@ public:
[[nodiscard]] bool externalReply() const override; [[nodiscard]] bool externalReply() const override;
[[nodiscard]] MsgId repliesInboxReadTill() const override; [[nodiscard]] MsgId repliesInboxReadTill() const override;
void setRepliesInboxReadTill(MsgId readTillId) override; void setRepliesInboxReadTill(
MsgId readTillId,
std::optional<int> unreadCount) override;
[[nodiscard]] MsgId computeRepliesInboxReadTillFull() const override; [[nodiscard]] MsgId computeRepliesInboxReadTillFull() const override;
[[nodiscard]] MsgId repliesOutboxReadTill() const override; [[nodiscard]] MsgId repliesOutboxReadTill() const override;
void setRepliesOutboxReadTill(MsgId readTillId) override; void setRepliesOutboxReadTill(MsgId readTillId) override;
@ -250,6 +255,9 @@ private:
void refreshRepliesText( void refreshRepliesText(
not_null<HistoryMessageViews*> views, not_null<HistoryMessageViews*> views,
bool forceResize = false); bool forceResize = false);
void setUnreadRepliesCount(
not_null<HistoryMessageViews*> views,
int count);
static void FillForwardedInfo( static void FillForwardedInfo(
CreateConfig &config, CreateConfig &config,

View file

@ -160,7 +160,6 @@ private:
bool _scrollDownIsShown = false; bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown; object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
int _messagesCount = -1; int _messagesCount = -1;
}; };

View file

@ -247,16 +247,24 @@ RepliesWidget::RepliesWidget(
data.progress); data.progress);
}, lifetime()); }, lifetime());
using MessageUpdateFlag = Data::MessageUpdate::Flag;
_history->session().changes().messageUpdates( _history->session().changes().messageUpdates(
Data::MessageUpdate::Flag::Destroyed MessageUpdateFlag::Destroyed
| MessageUpdateFlag::RepliesUnreadCount
) | rpl::start_with_next([=](const Data::MessageUpdate &update) { ) | rpl::start_with_next([=](const Data::MessageUpdate &update) {
if (update.item == _root) { if (update.flags & MessageUpdateFlag::Destroyed) {
_root = nullptr; if (update.item == _root) {
updatePinnedVisibility(); _root = nullptr;
controller->showBackFromStack(); updatePinnedVisibility();
} controller->showBackFromStack();
while (update.item == _replyReturn) { }
calculateNextReplyReturn(); while (update.item == _replyReturn) {
calculateNextReplyReturn();
}
return;
} else if ((update.item == _root)
&& (update.flags & MessageUpdateFlag::RepliesUnreadCount)) {
refreshUnreadCountBadge();
} }
}, lifetime()); }, lifetime());
@ -302,12 +310,15 @@ void RepliesWidget::sendReadTillRequest() {
_readRequestPending = false; _readRequestPending = false;
const auto api = &_history->session().api(); const auto api = &_history->session().api();
api->request(base::take(_readRequestId)).cancel(); api->request(base::take(_readRequestId)).cancel();
_readRequestId = api->request(MTPmessages_ReadDiscussion( _readRequestId = api->request(MTPmessages_ReadDiscussion(
_root->history()->peer->input, _root->history()->peer->input,
MTP_int(_root->id), MTP_int(_root->id),
MTP_int(_root->computeRepliesInboxReadTillFull()) MTP_int(_root->computeRepliesInboxReadTillFull())
)).done([=](const MTPBool &) { )).done(crl::guard(this, [=](const MTPBool &) {
}).send(); _readRequestId = 0;
reloadUnreadCountIfNeeded();
})).send();
} }
void RepliesWidget::setupRoot() { void RepliesWidget::setupRoot() {
@ -317,6 +328,7 @@ void RepliesWidget::setupRoot() {
_root = lookupRoot(); _root = lookupRoot();
if (_root) { if (_root) {
_areComments = computeAreComments(); _areComments = computeAreComments();
refreshUnreadCountBadge();
if (_readRequestPending) { if (_readRequestPending) {
sendReadTillRequest(); sendReadTillRequest();
} }
@ -370,6 +382,19 @@ bool RepliesWidget::computeAreComments() const {
return _root && _root->isDiscussionPost(); return _root && _root->isDiscussionPost();
} }
std::optional<int> RepliesWidget::computeUnreadCount() const {
if (!_root) {
return std::nullopt;
}
const auto views = _root->Get<HistoryMessageViews>();
if (!views) {
return std::nullopt;
}
return (views->repliesUnreadCount >= 0)
? std::make_optional(views->repliesUnreadCount)
: std::nullopt;
}
void RepliesWidget::setupComposeControls() { void RepliesWidget::setupComposeControls() {
auto slowmodeSecondsLeft = session().changes().peerFlagsValue( auto slowmodeSecondsLeft = session().changes().peerFlagsValue(
_history->peer, _history->peer,
@ -1143,6 +1168,7 @@ void RepliesWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] { _scrollDown->setClickedCallback([=] {
scrollDownClicked(); scrollDownClicked();
}); });
refreshUnreadCountBadge();
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) { base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::Wheel) { if (event->type() != QEvent::Wheel) {
return base::EventFilterResult::Continue; return base::EventFilterResult::Continue;
@ -1154,6 +1180,56 @@ void RepliesWidget::setupScrollDownButton() {
updateScrollDownVisibility(); updateScrollDownVisibility();
} }
void RepliesWidget::refreshUnreadCountBadge() {
if (!_root) {
return;
} else if (const auto count = computeUnreadCount()) {
_scrollDown->setUnreadCount(*count);
} else if (!_readRequestPending
&& !_readRequestTimer.isActive()
&& !_readRequestId) {
reloadUnreadCountIfNeeded();
}
}
void RepliesWidget::reloadUnreadCountIfNeeded() {
const auto views = _root ? _root->Get<HistoryMessageViews>() : nullptr;
if (!views || views->repliesUnreadCount >= 0) {
return;
} else if (views->repliesInboxReadTillId
< _root->computeRepliesInboxReadTillFull()) {
_readRequestTimer.callOnce(0);
} else if (!_reloadUnreadCountRequestId) {
const auto session = &_history->session();
const auto fullId = _root->fullId();
const auto apply = [session, fullId](int readTill, int unreadCount) {
if (const auto root = session->data().message(fullId)) {
root->setRepliesInboxReadTill(readTill, unreadCount);
if (const auto post = root->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(readTill, unreadCount);
}
}
};
const auto weak = Ui::MakeWeak(this);
_reloadUnreadCountRequestId = session->api().request(
MTPmessages_GetDiscussionMessage(
_history->peer->input,
MTP_int(_rootId))
).done([=](const MTPmessages_DiscussionMessage &result) {
if (weak) {
_reloadUnreadCountRequestId = 0;
}
result.match([&](const MTPDmessages_discussionMessage &data) {
session->data().processUsers(data.vusers());
session->data().processChats(data.vchats());
apply(
data.vread_inbox_max_id().value_or_empty(),
data.vunread_count().v);
});
}).send();
}
}
void RepliesWidget::scrollDownClicked() { void RepliesWidget::scrollDownClicked() {
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) { if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
showAtEnd(); showAtEnd();
@ -1692,11 +1768,21 @@ void RepliesWidget::readTill(not_null<HistoryItem*> item) {
} }
const auto was = _root->computeRepliesInboxReadTillFull(); const auto was = _root->computeRepliesInboxReadTillFull();
const auto now = item->id; const auto now = item->id;
const auto fast = item->out(); if (now < was) {
if (was < now) { return;
_root->setRepliesInboxReadTill(now); }
const auto views = _root->Get<HistoryMessageViews>();
const auto wasReadTillId = views ? views->repliesInboxReadTillId : 0;
const auto wasUnreadCount = views ? views->repliesUnreadCount : -1;
const auto unreadCount = _replies->fullUnreadCountAfter(
now,
wasReadTillId,
wasUnreadCount);
const auto fast = item->out() || !unreadCount.has_value();
if (was < now || (fast && now == was)) {
_root->setRepliesInboxReadTill(now, unreadCount);
if (const auto post = _root->lookupDiscussionPostOriginal()) { if (const auto post = _root->lookupDiscussionPostOriginal()) {
post->setRepliesInboxReadTill(now); post->setRepliesInboxReadTill(now, unreadCount);
} }
if (!_readRequestTimer.isActive()) { if (!_readRequestTimer.isActive()) {
_readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout); _readRequestTimer.callOnce(fast ? 0 : kReadRequestTimeout);

View file

@ -198,6 +198,7 @@ private:
[[nodiscard]] MsgId replyToId() const; [[nodiscard]] MsgId replyToId() const;
[[nodiscard]] HistoryItem *lookupRoot() const; [[nodiscard]] HistoryItem *lookupRoot() const;
[[nodiscard]] bool computeAreComments() const; [[nodiscard]] bool computeAreComments() const;
[[nodiscard]] std::optional<int> computeUnreadCount() const;
void orderWidgets(); void orderWidgets();
void pushReplyReturn(not_null<HistoryItem*> item); void pushReplyReturn(not_null<HistoryItem*> item);
@ -208,6 +209,8 @@ private:
void recountChatWidth(); void recountChatWidth();
void replyToMessage(FullMsgId itemId); void replyToMessage(FullMsgId itemId);
void refreshTopBarActiveChat(); void refreshTopBarActiveChat();
void refreshUnreadCountBadge();
void reloadUnreadCountIfNeeded();
void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type);
bool confirmSendingFiles( bool confirmSendingFiles(
@ -276,13 +279,13 @@ private:
bool _scrollDownIsShown = false; bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown; object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
bool _choosingAttach = false; bool _choosingAttach = false;
base::Timer _readRequestTimer; base::Timer _readRequestTimer;
bool _readRequestPending = false; bool _readRequestPending = false;
mtpRequestId _readRequestId = 0; mtpRequestId _readRequestId = 0;
mtpRequestId _reloadUnreadCountRequestId = 0;
bool _loaded = false; bool _loaded = false;
}; };

View file

@ -399,7 +399,8 @@ void SessionNavigation::showRepliesForMessage(
item->setRepliesMaxId(maxId->v); item->setRepliesMaxId(maxId->v);
} }
item->setRepliesInboxReadTill( item->setRepliesInboxReadTill(
data.vread_inbox_max_id().value_or_empty()); data.vread_inbox_max_id().value_or_empty(),
data.vunread_count().v);
item->setRepliesOutboxReadTill( item->setRepliesOutboxReadTill(
data.vread_outbox_max_id().value_or_empty()); data.vread_outbox_max_id().value_or_empty());
const auto post = _session->data().message(channelId, rootId); const auto post = _session->data().message(channelId, rootId);
@ -409,7 +410,8 @@ void SessionNavigation::showRepliesForMessage(
post->setRepliesMaxId(maxId->v); post->setRepliesMaxId(maxId->v);
} }
post->setRepliesInboxReadTill( post->setRepliesInboxReadTill(
data.vread_inbox_max_id().value_or_empty()); data.vread_inbox_max_id().value_or_empty(),
data.vunread_count().v);
post->setRepliesOutboxReadTill( post->setRepliesOutboxReadTill(
data.vread_outbox_max_id().value_or_empty()); data.vread_outbox_max_id().value_or_empty());
} }