Support unread state in sublists.

This commit is contained in:
John Preston 2025-05-19 14:59:57 +04:00
parent 4bc5e81513
commit b2c01991a6
16 changed files with 1287 additions and 339 deletions

View file

@ -2442,6 +2442,32 @@ void Updates::feedUpdate(const MTPUpdate &update) {
session().data().updateRepliesReadTill({ id, readTillId, true });
} break;
case mtpc_updateReadMonoForumInbox: {
const auto &d = update.c_updateReadMonoForumInbox();
const auto parentChatId = ChannelId(d.vchannel_id());
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
const auto readTillId = d.vread_max_id().v;
session().data().updateSublistReadTill({
parentChatId,
sublistPeerId,
readTillId,
false,
});
} break;
case mtpc_updateReadMonoForumOutbox: {
const auto &d = update.c_updateReadMonoForumOutbox();
const auto parentChatId = ChannelId(d.vchannel_id());
const auto sublistPeerId = peerFromMTP(d.vsaved_peer_id());
const auto readTillId = d.vread_max_id().v;
session().data().updateSublistReadTill({
parentChatId,
sublistPeerId,
readTillId,
true,
});
} break;
case mtpc_updateChannelAvailableMessages: {
auto &d = update.c_updateChannelAvailableMessages();
if (const auto channel = session().data().channelLoaded(d.vchannel_id())) {

View file

@ -3197,11 +3197,21 @@ void ApiWrap::sendAction(const SendAction &action) {
&& !action.options.shortcutId
&& !action.replaceMediaOf) {
const auto topicRootId = action.replyTo.topicRootId;
const auto monoforumPeerId = action.replyTo.monoforumPeerId;
const auto topic = topicRootId
? action.history->peer->forumTopicFor(topicRootId)
: nullptr;
const auto monoforum = monoforumPeerId
? action.history->peer->monoforum()
: nullptr;
const auto sublist = monoforum
? monoforum->sublistLoaded(
action.history->owner().peer(monoforumPeerId))
: nullptr;
if (topic) {
topic->readTillEnd();
} else if (sublist) {
sublist->readTillEnd();
} else {
_session->data().histories().readInbox(action.history);
}

View file

@ -362,8 +362,8 @@ void ForumTopic::subscribeToUnreadChanges() {
) | rpl::filter([=] {
return inChatList();
}) | rpl::start_with_next([=](
std::optional<int> previous,
std::optional<int> now) {
std::optional<int> previous,
std::optional<int> now) {
if (previous.value_or(0) != now.value_or(0)) {
_forum->recentTopicsInvalidate(this);
}

View file

@ -58,8 +58,6 @@ public:
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const;
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
MsgId afterId) const;
void requestUnreadCount();
void readTill(not_null<HistoryItem*> item);
@ -79,6 +77,8 @@ private:
void subscribeToUpdates();
void appendClientSideMessages(MessagesSlice &slice);
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
MsgId afterId) const;
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
[[nodiscard]] bool applyItemDestroyed(

View file

@ -9,7 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
#include "data/data_channel.h"
#include "data/data_peer.h"
#include "data/data_user.h"
#include "data/data_saved_sublist.h"
#include "data/data_session.h"
#include "history/history.h"
@ -34,13 +34,15 @@ SavedMessages::SavedMessages(
ChannelData *parentChat)
: _owner(owner)
, _parentChat(parentChat)
, _parentHistory(parentChat ? owner->history(parentChat).get() : nullptr)
, _owningHistory(parentChat ? owner->history(parentChat).get() : nullptr)
, _chatsList(
&_owner->session(),
FilterId(),
_owner->maxPinnedChatsLimitValue(this))
, _loadMore([=] { sendLoadMoreRequests(); }) {
if (_parentHistory && _parentHistory->inChatList()) {
// We don't assign _owningHistory for my Saved Messages here,
// because the data structures are not ready yet.
if (_owningHistory && _owningHistory->inChatList()) {
preloadSublists();
}
}
@ -51,10 +53,22 @@ bool SavedMessages::supported() const {
return !_unsupported;
}
void SavedMessages::markUnsupported() {
_unsupported = true;
}
ChannelData *SavedMessages::parentChat() const {
return _parentChat;
}
not_null<History*> SavedMessages::owningHistory() const {
if (!_owningHistory) {
const_cast<SavedMessages*>(this)->_owningHistory
= _owner->history(_owner->session().user());
}
return _owningHistory;
}
Session &SavedMessages::owner() const {
return *_owner;
}
@ -101,11 +115,6 @@ void SavedMessages::loadMore() {
_loadMore.call();
}
void SavedMessages::loadMore(not_null<SavedSublist*> sublist) {
_loadMoreSublistsScheduled.emplace(sublist);
_loadMore.call();
}
void SavedMessages::sendLoadMore() {
if (_loadMoreRequestId || _chatsList.loaded()) {
return;
@ -132,7 +141,7 @@ void SavedMessages::sendLoadMore() {
reorderLastSublists();
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
markUnsupported();
}
_chatsList.setLoaded();
_loadMoreRequestId = 0;
@ -150,7 +159,7 @@ void SavedMessages::loadPinned() {
_chatsListChanges.fire({});
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
markUnsupported();
} else {
_pinnedLoaded = true;
}
@ -158,82 +167,6 @@ void SavedMessages::loadPinned() {
}).send();
}
void SavedMessages::sendLoadMore(not_null<SavedSublist*> sublist) {
if (_loadMoreRequests.contains(sublist) || sublist->isFullLoaded()) {
return;
}
const auto &list = sublist->messages();
const auto offsetId = list.empty() ? MsgId(0) : list.back()->id;
const auto offsetDate = list.empty() ? MsgId(0) : list.back()->date();
const auto limit = offsetId ? kPerPage : kFirstPerPage;
using Flag = MTPmessages_GetSavedHistory::Flag;
const auto requestId = _owner->session().api().request(
MTPmessages_GetSavedHistory(
MTP_flags(_parentChat ? Flag::f_parent_peer : Flag(0)),
_parentChat ? _parentChat->input : MTPInputPeer(),
sublist->sublistPeer()->input,
MTP_int(offsetId),
MTP_int(offsetDate),
MTP_int(0), // add_offset
MTP_int(limit),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_long(0)) // hash
).done([=](const MTPmessages_Messages &result) {
auto count = 0;
auto list = (const QVector<MTPMessage>*)nullptr;
result.match([&](const MTPDmessages_channelMessages &data) {
if (const auto channel = _parentChat) {
channel->ptsReceived(data.vpts().v);
channel->processTopics(data.vtopics());
list = &data.vmessages().v;
count = data.vcount().v;
} else {
LOG(("API Error: messages.channelMessages in sublist."));
}
}, [](const MTPDmessages_messagesNotModified &) {
LOG(("API Error: messages.messagesNotModified in sublist."));
}, [&](const auto &data) {
owner().processUsers(data.vusers());
owner().processChats(data.vchats());
list = &data.vmessages().v;
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
count = int(list->size());
} else {
count = data.vcount().v;
}
});
_loadMoreRequests.remove(sublist);
if (!list) {
sublist->setFullLoaded();
return;
}
auto items = std::vector<not_null<HistoryItem*>>();
items.reserve(list->size());
for (const auto &message : *list) {
const auto item = owner().addNewMessage(
message,
{},
NewMessageType::Existing);
if (item) {
items.push_back(item);
}
}
sublist->append(std::move(items), count);
if (result.type() == mtpc_messages_messages) {
sublist->setFullLoaded();
}
}).fail([=](const MTP::Error &error) {
if (error.type() == u"SAVED_DIALOGS_UNSUPPORTED"_q) {
_unsupported = true;
}
sublist->setFullLoaded();
_loadMoreRequests.remove(sublist);
}).send();
_loadMoreRequests[sublist] = requestId;
}
void SavedMessages::apply(
const MTPmessages_SavedDialogs &result,
bool pinned) {
@ -291,8 +224,7 @@ void SavedMessages::apply(
offsetDate = item->date();
offsetId = topId;
lastValid = true;
const auto entry = sublist(peer);
entry->applyMaybeLast(item);
sublist(peer)->applyMonoforumDialog(data, item);
} else {
lastValid = false;
}
@ -321,9 +253,6 @@ void SavedMessages::sendLoadMoreRequests() {
if (_loadMoreScheduled) {
sendLoadMore();
}
for (const auto sublist : base::take(_loadMoreSublistsScheduled)) {
sendLoadMore(sublist);
}
}
void SavedMessages::apply(const MTPDupdatePinnedSavedDialogs &update) {
@ -371,7 +300,7 @@ void SavedMessages::apply(const MTPDupdateSavedDialogPinned &update) {
}
void SavedMessages::reorderLastSublists() {
if (!_parentHistory) {
if (!_parentChat) {
return;
}
@ -411,7 +340,7 @@ void SavedMessages::reorderLastSublists() {
}
}
++_lastSublistsVersion;
_parentHistory->updateChatListEntry();
owningHistory()->updateChatListEntry();
}
void SavedMessages::listMessageChanged(HistoryItem *from, HistoryItem *to) {
@ -426,11 +355,11 @@ int SavedMessages::recentSublistsListVersion() const {
void SavedMessages::recentSublistsInvalidate(
not_null<SavedSublist*> sublist) {
Expects(_parentHistory != nullptr);
Expects(_parentChat != nullptr);
if (ranges::contains(_lastSublists, sublist)) {
++_lastSublistsVersion;
_parentHistory->updateChatListEntry();
owningHistory()->updateChatListEntry();
}
}

View file

@ -26,7 +26,10 @@ public:
~SavedMessages();
[[nodiscard]] bool supported() const;
void markUnsupported();
[[nodiscard]] ChannelData *parentChat() const;
[[nodiscard]] not_null<History*> owningHistory() const;
[[nodiscard]] Session &owner() const;
[[nodiscard]] Main::Session &session() const;
@ -44,7 +47,6 @@ public:
void preloadSublists();
void loadMore();
void loadMore(not_null<SavedSublist*> sublist);
void apply(const MTPDupdatePinnedSavedDialogs &update);
void apply(const MTPDupdateSavedDialogPinned &update);
@ -64,12 +66,11 @@ private:
void reorderLastSublists();
void sendLoadMore();
void sendLoadMore(not_null<SavedSublist*> sublist);
void sendLoadMoreRequests();
const not_null<Session*> _owner;
ChannelData *_parentChat = nullptr;
History *_parentHistory = nullptr;
History *_owningHistory = nullptr;
rpl::event_stream<not_null<SavedSublist*>> _sublistDestroyed;
@ -78,7 +79,6 @@ private:
not_null<PeerData*>,
std::unique_ptr<SavedSublist>> _sublists;
base::flat_map<not_null<SavedSublist*>, mtpRequestId> _loadMoreRequests;
mtpRequestId _loadMoreRequestId = 0;
mtpRequestId _pinnedRequestId = 0;
@ -87,7 +87,6 @@ private:
PeerData *_offsetPeer = nullptr;
SingleQueuedInvokation _loadMore;
base::flat_set<not_null<SavedSublist*>> _loadMoreSublistsScheduled;
bool _loadMoreScheduled = false;
std::vector<not_null<SavedSublist*>> _lastSublists;

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "base/timer.h"
#include "data/data_thread.h"
#include "dialogs/ui/dialogs_message_view.h"
@ -16,31 +17,60 @@ class History;
namespace Data {
class Session;
class Histories;
class SavedMessages;
struct MessagePosition;
struct MessageUpdate;
struct SublistReadTillUpdate;
struct MessagesSlice;
class SavedSublist final : public Data::Thread {
public:
SavedSublist(not_null<SavedMessages*> parent, not_null<PeerData*> peer);
SavedSublist(
not_null<SavedMessages*> parent,
not_null<PeerData*> sublistPeer);
~SavedSublist();
[[nodiscard]] bool inMonoforum() const;
void apply(const SublistReadTillUpdate &update);
void apply(const MessageUpdate &update);
void applyDifferenceTooLong();
bool removeOne(not_null<HistoryItem*> item);
[[nodiscard]] rpl::producer<MessagesSlice> source(
MessagePosition aroundId,
int limitBefore,
int limitAfter);
[[nodiscard]] not_null<SavedMessages*> parent() const;
[[nodiscard]] not_null<History*> owningHistory() override;
[[nodiscard]] ChannelData *parentChat() const;
[[nodiscard]] not_null<PeerData*> sublistPeer() const;
[[nodiscard]] bool isHiddenAuthor() const;
[[nodiscard]] bool isFullLoaded() const;
[[nodiscard]] rpl::producer<> destroyed() const;
[[nodiscard]] auto messages() const
-> const std::vector<not_null<HistoryItem*>> &;
void growLastKnownServerMessageId(MsgId id);
void applyMaybeLast(not_null<HistoryItem*> item, bool added = false);
void removeOne(not_null<HistoryItem*> item);
void append(std::vector<not_null<HistoryItem*>> &&items, int fullCount);
void setFullLoaded(bool loaded = true);
void applyItemAdded(not_null<HistoryItem*> item);
void applyItemRemoved(MsgId id);
[[nodiscard]] rpl::producer<> changes() const;
[[nodiscard]] std::optional<int> fullCount() const;
[[nodiscard]] rpl::producer<int> fullCountValue() const;
[[nodiscard]] rpl::producer<std::optional<int>> maybeFullCount() const;
void loadFullCount();
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountCurrent() const;
[[nodiscard]] int displayedUnreadCount() const;
[[nodiscard]] rpl::producer<std::optional<int>> unreadCountValue() const;
void applyMonoforumDialog(
const MTPDmonoForumDialog &dialog,
not_null<HistoryItem*> topItem);
void readTillEnd();
void requestChatListMessage();
int fixedOnTopIndex() const override;
bool shouldBeInChatList() const override;
@ -57,9 +87,27 @@ public:
void hasUnreadMentionChanged(bool has) override;
void hasUnreadReactionChanged(bool has) override;
[[nodiscard]] HistoryItem *lastMessage() const;
[[nodiscard]] HistoryItem *lastServerMessage() const;
[[nodiscard]] bool lastMessageKnown() const;
[[nodiscard]] bool lastServerMessageKnown() const;
[[nodiscard]] MsgId lastKnownServerMessageId() const;
void setInboxReadTill(MsgId readTillId, std::optional<int> unreadCount);
[[nodiscard]] MsgId inboxReadTillId() const;
[[nodiscard]] MsgId computeInboxReadTillFull() const;
void setOutboxReadTill(MsgId readTillId);
[[nodiscard]] MsgId computeOutboxReadTillFull() const;
[[nodiscard]] bool isServerSideUnread(
not_null<const HistoryItem*> item) const override;
void requestUnreadCount();
void readTill(not_null<HistoryItem*> item);
void readTill(MsgId tillId);
void chatListPreloadData() override;
void paintUserpic(
Painter &p,
@ -70,25 +118,75 @@ public:
-> HistoryView::SendActionPainter* override;
private:
struct Viewer;
enum class Flag : uchar {
ResolveChatListMessage = (1 << 0),
FullLoaded = (1 << 1),
InMonoforum = (1 << 1),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
using Flags = base::flags<Flag>;
bool hasOrphanMediaGroupPart() const;
[[nodiscard]] Histories &histories();
void subscribeToUnreadChanges();
[[nodiscard]] Dialogs::UnreadState unreadStateFor(
int count,
bool known) const;
void setLastMessage(HistoryItem *item);
void setLastServerMessage(HistoryItem *item);
void setChatListMessage(HistoryItem *item);
void allowChatListMessageResolve();
void resolveChatListMessageGroup();
const not_null<SavedMessages*> _parent;
const not_null<History*> _history;
void changeUnreadCountByMessage(MsgId id, int delta);
void setUnreadCount(std::optional<int> count);
void readTill(MsgId tillId, HistoryItem *tillIdItem);
void checkReadTillEnd();
void sendReadTillRequest();
void reloadUnreadCountIfNeeded();
std::vector<not_null<HistoryItem*>> _items;
std::optional<int> _fullCount;
rpl::event_stream<> _changed;
[[nodiscard]] bool buildFromData(not_null<Viewer*> viewer);
[[nodiscard]] bool applyUpdate(const MessageUpdate &update);
void appendClientSideMessages(MessagesSlice &slice);
[[nodiscard]] std::optional<int> computeUnreadCountLocally(
MsgId afterId) const;
bool processMessagesIsEmpty(const MTPmessages_Messages &result);
void loadAround(MsgId id);
void loadBefore();
void loadAfter();
const not_null<SavedMessages*> _parent;
const not_null<History*> _sublistHistory;
MsgId _lastKnownServerMessageId = 0;
std::vector<MsgId> _list;
std::optional<int> _skippedBefore;
std::optional<int> _skippedAfter;
rpl::variable<std::optional<int>> _fullCount;
rpl::event_stream<> _listChanges;
rpl::event_stream<> _instantChanges;
std::optional<MsgId> _loadingAround;
rpl::variable<std::optional<int>> _unreadCount;
MsgId _inboxReadTillId = 0;
MsgId _outboxReadTillId = 0;
Flags _flags;
std::optional<HistoryItem*> _lastMessage;
std::optional<HistoryItem*> _lastServerMessage;
std::optional<HistoryItem*> _chatListMessage;
base::flat_set<FullMsgId> _requestedGroups;
int _beforeId = 0;
int _afterId = 0;
base::Timer _readRequestTimer;
mtpRequestId _readRequestId = 0;
mtpRequestId _reloadUnreadCountRequestId = 0;
rpl::lifetime _lifetime;
};
} // namespace Data

View file

@ -341,6 +341,19 @@ void Session::subscribeForTopicRepliesLists() {
}
}, _lifetime);
sublistReadTillUpdates(
) | rpl::start_with_next([=](const SublistReadTillUpdate &update) {
if (const auto parentChat = channelLoaded(update.parentChatId)) {
if (const auto monoforum = parentChat->monoforum()) {
const auto sublistPeerId = update.sublistPeerId;
const auto peer = monoforum->owner().peer(sublistPeerId);
if (const auto sublist = monoforum->sublistLoaded(peer)) {
sublist->apply(update);
}
}
}
}, _lifetime);
session().changes().messageUpdates(
MessageUpdate::Flag::NewAdded
| MessageUpdate::Flag::NewMaybeAdded
@ -349,6 +362,11 @@ void Session::subscribeForTopicRepliesLists() {
) | rpl::start_with_next([=](const MessageUpdate &update) {
if (const auto topic = update.item->topic()) {
topic->replies()->apply(update);
} else if (update.flags == MessageUpdate::Flag::ReplyToTopAdded) {
// Not interested in this one for sublist.
return;
} else if (const auto sublist = update.item->savedSublist()) {
sublist->apply(update);
}
}, _lifetime);
@ -2914,6 +2932,15 @@ auto Session::repliesReadTillUpdates() const
return _repliesReadTillUpdates.events();
}
void Session::updateSublistReadTill(SublistReadTillUpdate update) {
_sublistReadTillUpdates.fire(std::move(update));
}
auto Session::sublistReadTillUpdates() const
-> rpl::producer<SublistReadTillUpdate> {
return _sublistReadTillUpdates.events();
}
int Session::computeUnreadBadge(const Dialogs::UnreadState &state) const {
const auto all = Core::App().settings().includeMutedCounter();
return std::max(state.marks - (all ? 0 : state.marksMuted), 0)

View file

@ -80,6 +80,13 @@ struct RepliesReadTillUpdate {
bool out = false;
};
struct SublistReadTillUpdate {
ChannelId parentChatId;
PeerId sublistPeerId;
MsgId readTillId;
bool out = false;
};
struct GiftUpdate {
enum class Action : uchar {
Save,
@ -565,6 +572,10 @@ public:
[[nodiscard]] auto repliesReadTillUpdates() const
-> rpl::producer<RepliesReadTillUpdate>;
void updateSublistReadTill(SublistReadTillUpdate update);
[[nodiscard]] auto sublistReadTillUpdates() const
-> rpl::producer<SublistReadTillUpdate>;
void selfDestructIn(not_null<HistoryItem*> item, crl::time delay);
[[nodiscard]] not_null<PhotoData*> photo(PhotoId id);
@ -1004,6 +1015,7 @@ private:
rpl::event_stream<ChatListEntryRefresh> _chatListEntryRefreshes;
rpl::event_stream<> _unreadBadgeChanges;
rpl::event_stream<RepliesReadTillUpdate> _repliesReadTillUpdates;
rpl::event_stream<SublistReadTillUpdate> _sublistReadTillUpdates;
rpl::event_stream<SentToScheduled> _sentToScheduled;
rpl::event_stream<SentFromScheduled> _sentFromScheduled;

View file

@ -163,6 +163,9 @@ void History::itemRemoved(not_null<HistoryItem*> item) {
if (const auto topic = item->topic()) {
topic->applyItemRemoved(item->id);
}
if (const auto sublist = item->savedSublist()) {
sublist->applyItemRemoved(item->id);
}
if (const auto chat = peer->asChat()) {
if (const auto to = chat->getMigrateToChannel()) {
if (const auto history = owner().historyLoaded(to)) {
@ -1311,6 +1314,9 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (const auto topic = item->topic()) {
topic->applyItemAdded(item);
}
if (const auto sublist = item->savedSublist()) {
sublist->applyItemAdded(item);
}
if (const auto media = item->media()) {
if (const auto gift = media->gift()) {
if (const auto unique = gift->unique.get()) {

View file

@ -789,16 +789,6 @@ HistoryItem::~HistoryItem() {
if (const auto reply = Get<HistoryMessageReply>()) {
reply->clearData(this);
}
if (const auto saved = Get<HistoryMessageSaved>()) {
if (saved->savedMessagesSublist) {
saved->savedMessagesSublist->removeOne(this);
} else if (const auto monoforum = _history->peer->monoforum()) {
const auto peer = _history->owner().peer(saved->sublistPeerId);
if (const auto sublist = monoforum->sublistLoaded(peer)) {
sublist->removeOne(this);
}
}
}
clearDependencyMessage();
applyTTL(0);
}

View file

@ -191,6 +191,13 @@ object_ptr<Window::SectionWidget> ChatMemento::createWidget(
_list.setScrollTopState(ListMemento::ScrollTopState{
Data::MinMessagePosition
});
} else if (!_list.aroundPosition().fullId
&& _id.sublist
&& _id.sublist->computeInboxReadTillFull() == MsgId(1)) {
_list.setAroundPosition(Data::MinMessagePosition);
_list.setScrollTopState(ListMemento::ScrollTopState{
Data::MinMessagePosition
});
}
auto result = object_ptr<ChatWidget>(parent, controller, _id);
result->setInternalState(geometry, this);
@ -394,7 +401,9 @@ ChatWidget::ChatWidget(
}
}, lifetime());
if (!_topic) {
if (_sublist) {
subscribeToSublist();
} else if (!_topic) {
_history->session().changes().historyUpdates(
_history,
Data::HistoryUpdate::Flag::OutboxRead
@ -2455,6 +2464,19 @@ void ChatWidget::setReplies(std::shared_ptr<Data::RepliesList> replies) {
}, _repliesLifetime);
}
void ChatWidget::subscribeToSublist() {
Expects(_sublist != nullptr);
_sublist->unreadCountValue(
) | rpl::start_with_next([=](std::optional<int> count) {
refreshUnreadCountBadge(count);
}, lifetime());
refreshUnreadCountBadge(_sublist->unreadCountKnown()
? _sublist->unreadCountCurrent()
: std::optional<int>());
}
void ChatWidget::restoreState(not_null<ChatMemento*> memento) {
if (auto replies = memento->getReplies()) {
setReplies(std::move(replies));
@ -2792,54 +2814,20 @@ rpl::producer<Data::MessagesSlice> ChatWidget::sublistSource(
const auto messageId = aroundId.fullId.msg
? aroundId.fullId.msg
: (ServerMaxMsgId - 1);
return [=](auto consumer) {
const auto pushSlice = [=] {
auto result = Data::MessagesSlice();
result.fullCount = _sublist->fullCount();
_topBar->setCustomTitle(result.fullCount
? tr::lng_forum_messages(
tr::now,
lt_count_decimal,
*result.fullCount)
: tr::lng_contacts_loading(tr::now));
const auto &messages = _sublist->messages();
const auto i = ranges::lower_bound(
messages,
messageId,
ranges::greater(),
[](not_null<HistoryItem*> item) { return item->id; });
const auto before = int(end(messages) - i);
const auto useBefore = std::min(before, limitBefore);
const auto after = int(i - begin(messages));
const auto useAfter = std::min(after, limitAfter);
const auto from = i - useAfter;
const auto till = i + useBefore;
auto nearestDistance = std::numeric_limits<int64>::max();
result.ids.reserve(useAfter + useBefore);
for (auto j = till; j != from;) {
const auto item = *--j;
result.ids.push_back(item->fullId());
const auto distance = std::abs((messageId - item->id).bare);
if (nearestDistance > distance) {
nearestDistance = distance;
result.nearestToAround = result.ids.back();
}
}
result.skippedAfter = after - useAfter;
result.skippedBefore = result.fullCount
? (*result.fullCount - after - useBefore)
: std::optional<int>();
if (!result.fullCount || useBefore < limitBefore) {
_sublist->parent()->loadMore(_sublist);
}
markLoaded();
consumer.put_next(std::move(result));
};
auto lifetime = rpl::lifetime();
_sublist->changes() | rpl::start_with_next(pushSlice, lifetime);
pushSlice();
return lifetime;
};
return _sublist->source(
aroundId,
limitBefore,
limitAfter
) | rpl::before_next([=](const Data::MessagesSlice &result) {
// after_next makes a copy of value.
_topBar->setCustomTitle(result.fullCount
? tr::lng_forum_messages(
tr::now,
lt_count_decimal,
*result.fullCount)
: tr::lng_contacts_loading(tr::now));
markLoaded();
});
}
bool ChatWidget::listAllowsMultiSelect() {
@ -2882,6 +2870,8 @@ void ChatWidget::listSelectionChanged(SelectedItems &&items) {
void ChatWidget::listMarkReadTill(not_null<HistoryItem*> item) {
if (_replies) {
_replies->readTill(item);
} else if (_sublist) {
_sublist->readTill(item);
}
}
@ -2892,16 +2882,22 @@ void ChatWidget::listMarkContentsRead(
MessagesBarData ChatWidget::listMessagesBar(
const std::vector<not_null<Element*>> &elements) {
if (_sublist || elements.empty()) {
if ((!_sublist && !_replies) || elements.empty()) {
return {};
}
const auto till = _replies->computeInboxReadTillFull();
const auto till = _replies
? _replies->computeInboxReadTillFull()
: _sublist->computeInboxReadTillFull();
const auto hidden = (till < 2);
for (auto i = 0, count = int(elements.size()); i != count; ++i) {
const auto item = elements[i]->data();
if (item->isRegular() && item->id > till) {
if (item->out() || !item->replyToId()) {
_replies->readTill(item);
if (item->out() || (_replies && !item->replyToId())) {
if (_replies) {
_replies->readTill(item);
} else {
_sublist->readTill(item);
}
} else {
return {
.bar = {
@ -2960,9 +2956,12 @@ bool ChatWidget::listElementHideReply(not_null<const Element*> view) {
}
bool ChatWidget::listElementShownUnread(not_null<const Element*> view) {
const auto item = view->data();
return _replies
? _replies->isServerSideUnread(view->data())
: view->data()->unread(view->data()->history());
? _replies->isServerSideUnread(item)
: _sublist
? _sublist->isServerSideUnread(item)
: item->unread(item->history());
}
bool ChatWidget::listIsGoodForAroundPosition(
@ -2973,7 +2972,7 @@ bool ChatWidget::listIsGoodForAroundPosition(
void ChatWidget::listSendBotCommand(
const QString &command,
const FullMsgId &context) {
if (!_sublist) {
if (!_sublist || _sublist->parentChat()) {
sendBotCommandWithOptions(command, context, {});
}
}

View file

@ -265,6 +265,7 @@ private:
void setupRootView();
void setupTopicViewer();
void subscribeToTopic();
void subscribeToSublist();
void subscribeToPinnedMessages();
void setTopic(Data::ForumTopic *topic);

View file

@ -588,8 +588,8 @@ rpl::producer<int> SavedSublistCountValue(
not_null<PeerData*> peer) {
const auto saved = &peer->owner().savedMessages();
const auto sublist = saved->sublist(peer);
if (!sublist->fullCount()) {
saved->loadMore(sublist);
if (!sublist->fullCount().has_value()) {
sublist->loadFullCount();
return rpl::single(0) | rpl::then(sublist->fullCountValue());
}
return sublist->fullCountValue();

View file

@ -433,7 +433,7 @@ updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Updat
updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update;
updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update;
updateGroupCallChainBlocks#a477288f call:InputGroupCall sub_chain_id:int blocks:Vector<bytes> next_offset:int = Update;
updateReadMonoForumInbox#bcf34712 flags:# channel_id:long saved_peer_id:Peer read_max_id:int = Update;
updateReadMonoForumInbox#77b0e372 channel_id:long saved_peer_id:Peer read_max_id:int = Update;
updateReadMonoForumOutbox#a4a79376 channel_id:long saved_peer_id:Peer read_max_id:int = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -2395,6 +2395,7 @@ messages.savePreparedInlineMessage#f21f7f2f flags:# result:InputBotInlineResult
messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage;
messages.searchStickers#29b1c66a flags:# emojis:flags.0?true q:string emoticon:string lang_code:Vector<string> offset:int limit:int hash:long = messages.FoundStickers;
messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector<int> = Bool;
messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids:Vector<InputPeer> = messages.SavedDialogs;
messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool;
updates.getState#edd4882a = updates.State;