mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 06:37:24 +02:00
Switch between pinned messages in chat.
This commit is contained in:
parent
ec35e3f081
commit
b9f40e35cd
12 changed files with 466 additions and 69 deletions
|
@ -564,6 +564,8 @@ PRIVATE
|
|||
history/view/history_view_message.cpp
|
||||
history/view/history_view_message.h
|
||||
history/view/history_view_object.h
|
||||
history/view/history_view_pinned_tracker.cpp
|
||||
history/view/history_view_pinned_tracker.h
|
||||
history/view/history_view_replies_section.cpp
|
||||
history/view/history_view_replies_section.h
|
||||
history/view/history_view_schedule_box.cpp
|
||||
|
|
|
@ -161,6 +161,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_deleted" = "Deleted Account";
|
||||
"lng_deleted_message" = "Deleted message";
|
||||
"lng_pinned_message" = "Pinned message";
|
||||
"lng_pinned_previous" = "Previous message";
|
||||
"lng_pinned_poll" = "Pinned poll";
|
||||
"lng_pinned_quiz" = "Pinned quiz";
|
||||
"lng_pinned_unpin_sure" = "Would you like to unpin this message?";
|
||||
|
|
|
@ -469,12 +469,14 @@ void PeerData::ensurePinnedMessagesCreated() {
|
|||
if (!_pinnedMessages) {
|
||||
_pinnedMessages = std::make_unique<Data::PinnedMessages>(
|
||||
peerToChannel(id));
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
void PeerData::removeEmptyPinnedMessages() {
|
||||
if (_pinnedMessages && _pinnedMessages->empty()) {
|
||||
_pinnedMessages = nullptr;
|
||||
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,8 +513,8 @@ void PeerData::addPinnedMessage(MsgId messageId) {
|
|||
|
||||
void PeerData::addPinnedSlice(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgId from,
|
||||
MsgId till) {
|
||||
MsgRange noSkipRange,
|
||||
std::optional<int> count) {
|
||||
const auto min = [&] {
|
||||
if (const auto channel = asChannel()) {
|
||||
return channel->availableMinId();
|
||||
|
@ -522,14 +524,14 @@ void PeerData::addPinnedSlice(
|
|||
ids.erase(
|
||||
ranges::remove_if(ids, [&](MsgId id) { return id <= min; }),
|
||||
end(ids));
|
||||
if (from <= min) {
|
||||
from = 0;
|
||||
if (noSkipRange.from <= min) {
|
||||
noSkipRange.from = 0;
|
||||
}
|
||||
if (ids.empty() && !_pinnedMessages) {
|
||||
return;
|
||||
}
|
||||
ensurePinnedMessagesCreated();
|
||||
_pinnedMessages->add(std::move(ids), from, till, std::nullopt);
|
||||
_pinnedMessages->add(std::move(ids), noSkipRange, std::nullopt);
|
||||
}
|
||||
|
||||
void PeerData::removePinnedMessage(MsgId messageId) {
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Data {
|
|||
|
||||
class Session;
|
||||
class PinnedMessages;
|
||||
struct PinnedAroundId;
|
||||
|
||||
int PeerColorIndex(PeerId peerId);
|
||||
int PeerColorIndex(int32 bareId);
|
||||
|
@ -331,8 +332,14 @@ public:
|
|||
void setTopPinnedMessageId(MsgId messageId);
|
||||
void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId);
|
||||
void addPinnedMessage(MsgId messageId);
|
||||
void addPinnedSlice(std::vector<MsgId> &&ids, MsgId from, MsgId till);
|
||||
void addPinnedSlice(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgRange noSkipRange,
|
||||
std::optional<int> count);
|
||||
void removePinnedMessage(MsgId messageId);
|
||||
Data::PinnedMessages *currentPinnedMessages() const {
|
||||
return _pinnedMessages.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool canExportChatHistory() const;
|
||||
|
||||
|
|
|
@ -25,6 +25,25 @@ MsgId PinnedMessages::topId() const {
|
|||
return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg;
|
||||
}
|
||||
|
||||
rpl::producer<PinnedAroundId> PinnedMessages::viewer(
|
||||
MsgId aroundId,
|
||||
int limit) const {
|
||||
return _list.viewer(MessagesQuery{
|
||||
.aroundId = position(aroundId),
|
||||
.limitBefore = limit,
|
||||
.limitAfter = limit
|
||||
}) | rpl::map([](const MessagesResult &result) {
|
||||
auto data = PinnedAroundId();
|
||||
data.fullCount = result.count;
|
||||
data.skippedBefore = result.skippedBefore;
|
||||
data.skippedAfter = result.skippedAfter;
|
||||
data.ids = result.messageIds | ranges::view::transform(
|
||||
[](MessagePosition position) { return position.fullId.msg; }
|
||||
) | ranges::to_vector;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
MessagePosition PinnedMessages::position(MsgId id) const {
|
||||
return MessagePosition{
|
||||
.fullId = FullMsgId(_channelId, id),
|
||||
|
@ -37,8 +56,7 @@ void PinnedMessages::add(MsgId messageId) {
|
|||
|
||||
void PinnedMessages::add(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgId from,
|
||||
MsgId till,
|
||||
MsgRange range,
|
||||
std::optional<int> count) {
|
||||
auto positions = ids | ranges::view::transform([&](MsgId id) {
|
||||
return position(id);
|
||||
|
@ -47,16 +65,14 @@ void PinnedMessages::add(
|
|||
_list.addSlice(
|
||||
std::move(positions),
|
||||
MessagesRange{
|
||||
.from = from ? position(from) : MinMessagePosition,
|
||||
.till = position(till)
|
||||
.from = range.from ? position(range.from) : MinMessagePosition,
|
||||
.till = position(range.till)
|
||||
},
|
||||
count);
|
||||
}
|
||||
|
||||
void PinnedMessages::remove(MsgId messageId) {
|
||||
_list.removeOne(MessagePosition{
|
||||
.fullId = FullMsgId(0, messageId),
|
||||
});
|
||||
_list.removeOne(position(messageId));
|
||||
}
|
||||
|
||||
void PinnedMessages::setTopId(MsgId messageId) {
|
||||
|
@ -70,19 +86,15 @@ void PinnedMessages::setTopId(MsgId messageId) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
const auto position = MessagePosition{
|
||||
.fullId = FullMsgId(0, messageId),
|
||||
};
|
||||
const auto wrapped = position(messageId);
|
||||
_list.addSlice(
|
||||
{ position },
|
||||
{ .from = position, .till = MaxMessagePosition },
|
||||
{ wrapped },
|
||||
{ .from = wrapped, .till = MaxMessagePosition },
|
||||
std::nullopt);
|
||||
}
|
||||
|
||||
void PinnedMessages::clearLessThanId(MsgId messageId) {
|
||||
_list.removeLessThan(MessagePosition{
|
||||
.fullId = FullMsgId(0, messageId),
|
||||
});
|
||||
_list.removeLessThan(position(messageId));
|
||||
}
|
||||
|
||||
} // namespace Data
|
||||
|
|
|
@ -8,21 +8,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#pragma once
|
||||
|
||||
#include "data/data_messages.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
namespace Data {
|
||||
|
||||
class PinnedMessages final {
|
||||
struct PinnedAroundId {
|
||||
std::vector<MsgId> ids;
|
||||
std::optional<int> skippedBefore;
|
||||
std::optional<int> skippedAfter;
|
||||
std::optional<int> fullCount;
|
||||
};
|
||||
|
||||
class PinnedMessages final : public base::has_weak_ptr {
|
||||
public:
|
||||
explicit PinnedMessages(ChannelId channelId);
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] MsgId topId() const;
|
||||
[[nodiscard]] rpl::producer<PinnedAroundId> viewer(
|
||||
MsgId aroundId,
|
||||
int limit) const;
|
||||
|
||||
void add(MsgId messageId);
|
||||
void add(
|
||||
std::vector<MsgId> &&ids,
|
||||
MsgId from,
|
||||
MsgId till,
|
||||
MsgRange range,
|
||||
std::optional<int> count);
|
||||
void remove(MsgId messageId);
|
||||
|
||||
|
|
|
@ -1161,7 +1161,7 @@ void History::addEdgesToSharedMedia() {
|
|||
{},
|
||||
{ from, till }));
|
||||
}
|
||||
peer->addPinnedSlice({}, from, till);
|
||||
peer->addPinnedSlice({}, { from, till }, std::nullopt);
|
||||
}
|
||||
|
||||
void History::addOlderSlice(const QVector<MTPMessage> &slice) {
|
||||
|
@ -1358,7 +1358,7 @@ void History::addToSharedMedia(
|
|||
{ from, till }));
|
||||
}
|
||||
}
|
||||
peer->addPinnedSlice(std::move(pinned), from, till);
|
||||
peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt);
|
||||
}
|
||||
|
||||
void History::calculateFirstUnreadMessage() {
|
||||
|
@ -1785,16 +1785,19 @@ TimeId History::adjustedChatListTimeId() const {
|
|||
}
|
||||
|
||||
void History::countScrollState(int top) {
|
||||
countScrollTopItem(top);
|
||||
if (scrollTopItem) {
|
||||
scrollTopOffset = (top - scrollTopItem->block()->y() - scrollTopItem->y());
|
||||
}
|
||||
std::tie(scrollTopItem, scrollTopOffset) = findItemAndOffset(top);
|
||||
}
|
||||
|
||||
void History::countScrollTopItem(int top) {
|
||||
auto History::findItemAndOffset(int top) const -> std::pair<Element*, int> {
|
||||
if (const auto element = findScrollTopItem(top)) {
|
||||
return { element, (top - element->block()->y() - element->y()) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto History::findScrollTopItem(int top) const -> Element* {
|
||||
if (isEmpty()) {
|
||||
forgetScrollState();
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto itemIndex = 0;
|
||||
|
@ -1813,8 +1816,7 @@ void History::countScrollTopItem(int top) {
|
|||
const auto view = block->messages[itemIndex].get();
|
||||
itemTop = block->y() + view->y();
|
||||
if (itemTop <= top) {
|
||||
scrollTopItem = view;
|
||||
return;
|
||||
return view;
|
||||
}
|
||||
}
|
||||
if (--blockIndex >= 0) {
|
||||
|
@ -1824,27 +1826,24 @@ void History::countScrollTopItem(int top) {
|
|||
}
|
||||
} while (true);
|
||||
|
||||
scrollTopItem = blocks.front()->messages.front().get();
|
||||
} else {
|
||||
// go forward through history while we don't find the last item that starts above
|
||||
for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
|
||||
const auto &block = blocks[blockIndex];
|
||||
for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
|
||||
itemTop = block->y() + block->messages[itemIndex]->y();
|
||||
if (itemTop > top) {
|
||||
Assert(itemIndex > 0 || blockIndex > 0);
|
||||
if (itemIndex > 0) {
|
||||
scrollTopItem = block->messages[itemIndex - 1].get();
|
||||
} else {
|
||||
scrollTopItem = blocks[blockIndex - 1]->messages.back().get();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
itemIndex = 0;
|
||||
}
|
||||
scrollTopItem = blocks.back()->messages.back().get();
|
||||
return blocks.front()->messages.front().get();
|
||||
}
|
||||
// go forward through history while we don't find the last item that starts above
|
||||
for (auto blocksCount = int(blocks.size()); blockIndex < blocksCount; ++blockIndex) {
|
||||
const auto &block = blocks[blockIndex];
|
||||
for (auto itemsCount = int(block->messages.size()); itemIndex < itemsCount; ++itemIndex) {
|
||||
itemTop = block->y() + block->messages[itemIndex]->y();
|
||||
if (itemTop > top) {
|
||||
Assert(itemIndex > 0 || blockIndex > 0);
|
||||
if (itemIndex > 0) {
|
||||
return block->messages[itemIndex - 1].get();
|
||||
}
|
||||
return blocks[blockIndex - 1]->messages.back().get();
|
||||
}
|
||||
}
|
||||
itemIndex = 0;
|
||||
}
|
||||
return blocks.back()->messages.back().get();
|
||||
}
|
||||
|
||||
void History::getNextScrollTopItem(HistoryBlock *block, int32 i) {
|
||||
|
|
|
@ -366,6 +366,8 @@ public:
|
|||
// of the displayed window relative to the history start coordinate
|
||||
void countScrollState(int top);
|
||||
|
||||
[[nodiscard]] std::pair<Element*, int> findItemAndOffset(int top) const;
|
||||
|
||||
MsgId nextNonHistoryEntryId();
|
||||
|
||||
bool folderKnown() const override;
|
||||
|
@ -422,7 +424,7 @@ private:
|
|||
void getNextScrollTopItem(HistoryBlock *block, int32 i);
|
||||
|
||||
// helper method for countScrollState(int top)
|
||||
void countScrollTopItem(int top);
|
||||
[[nodiscard]] Element *findScrollTopItem(int top) const;
|
||||
|
||||
// this method just removes a block from the blocks list
|
||||
// when the last item from this block was detached and
|
||||
|
|
|
@ -88,6 +88,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "apiwrap.h"
|
||||
#include "history/view/history_view_top_bar_widget.h"
|
||||
#include "history/view/history_view_contact_status.h"
|
||||
#include "history/view/history_view_pinned_tracker.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "ui/widgets/popup_menu.h"
|
||||
#include "ui/text_options.h"
|
||||
|
@ -1697,7 +1698,8 @@ void HistoryWidget::showHistory(
|
|||
_history->showAtMsgId = _showAtMsgId;
|
||||
|
||||
destroyUnreadBarOnClose();
|
||||
destroyPinnedBar();
|
||||
showPinnedMessage(FullMsgId());
|
||||
_pinnedTracker = nullptr;
|
||||
_membersDropdown.destroy();
|
||||
_scrollToAnimation.stop();
|
||||
|
||||
|
@ -1811,6 +1813,7 @@ void HistoryWidget::showHistory(
|
|||
|
||||
_updateHistoryItems.stop();
|
||||
|
||||
setupPinnedTracker();
|
||||
pinnedMsgVisibilityUpdated();
|
||||
if (_history->scrollTopItem
|
||||
|| (_migrated && _migrated->scrollTopItem)
|
||||
|
@ -2732,6 +2735,7 @@ void HistoryWidget::delayedShowAt(MsgId showAtMsgId) {
|
|||
void HistoryWidget::onScroll() {
|
||||
preloadHistoryIfNeeded();
|
||||
visibleAreaUpdated();
|
||||
updatePinnedViewer();
|
||||
if (!_synteticScrollEvent) {
|
||||
_lastUserScrolled = crl::now();
|
||||
}
|
||||
|
@ -3211,6 +3215,7 @@ void HistoryWidget::doneShow() {
|
|||
handlePendingHistoryUpdate();
|
||||
}
|
||||
preloadHistoryIfNeeded();
|
||||
updatePinnedViewer();
|
||||
checkHistoryActivation();
|
||||
App::wnd()->setInnerFocus();
|
||||
}
|
||||
|
@ -5226,14 +5231,66 @@ void HistoryWidget::updatePinnedBar(bool force) {
|
|||
}
|
||||
}
|
||||
|
||||
void HistoryWidget::updatePinnedViewer() {
|
||||
if (_firstLoadRequest
|
||||
|| _delayedShowAtRequest
|
||||
|| _scroll->isHidden()
|
||||
|| !_history
|
||||
|| !_historyInited) {
|
||||
return;
|
||||
}
|
||||
const auto [item, offset] = [&] {
|
||||
auto visibleTop = _scroll->scrollTop();
|
||||
if (_migrated
|
||||
&& _history->loadedAtBottom()
|
||||
&& _migrated->loadedAtTop()) {
|
||||
visibleTop -= _migrated->height();
|
||||
}
|
||||
auto [item, offset] = _history->findItemAndOffset(visibleTop);
|
||||
while (item && !IsServerMsgId(item->data()->id)) {
|
||||
offset -= item->height();
|
||||
item = item->nextInBlocks();
|
||||
}
|
||||
return std::pair(item, offset);
|
||||
}();
|
||||
const auto last = _history->peer->topPinnedMessageId();
|
||||
const auto lessThanId = item
|
||||
? (item->data()->id + (offset > 0 ? 1 : 0))
|
||||
: (last + 1);
|
||||
_pinnedTracker->trackAround(lessThanId);
|
||||
}
|
||||
|
||||
void HistoryWidget::setupPinnedTracker() {
|
||||
Expects(_history != nullptr);
|
||||
|
||||
_pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
|
||||
_pinnedTracker->shownMessageId(
|
||||
) | rpl::start_with_next([=](MsgId messageId) {
|
||||
showPinnedMessage({ peerToChannel(_peer->id), messageId });
|
||||
}, _list->lifetime());
|
||||
}
|
||||
|
||||
void HistoryWidget::showPinnedMessage(FullMsgId id) {
|
||||
if (_pinnedId == id) {
|
||||
return;
|
||||
}
|
||||
_pinnedId = id;
|
||||
if (pinnedMsgVisibilityUpdated()) {
|
||||
updateHistoryGeometry();
|
||||
updateControlsVisibility();
|
||||
updateControlsGeometry();
|
||||
this->update();
|
||||
}
|
||||
}
|
||||
|
||||
bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
||||
auto result = false;
|
||||
auto pinnedId = _peer->topPinnedMessageId();
|
||||
auto pinnedId = _pinnedId;
|
||||
if (pinnedId && !_peer->canPinMessages()) {
|
||||
const auto hiddenId = session().settings().hiddenPinnedMessageId(
|
||||
_peer->id);
|
||||
if (hiddenId == pinnedId) {
|
||||
pinnedId = 0;
|
||||
if (hiddenId == pinnedId.msg) {
|
||||
pinnedId = FullMsgId();
|
||||
} else if (hiddenId) {
|
||||
session().settings().setHiddenPinnedMessageId(_peer->id, 0);
|
||||
session().saveSettings();
|
||||
|
@ -5241,7 +5298,7 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
|||
}
|
||||
if (pinnedId) {
|
||||
if (!_pinnedBar) {
|
||||
_pinnedBar = std::make_unique<PinnedBar>(pinnedId, this);
|
||||
_pinnedBar = std::make_unique<PinnedBar>(pinnedId.msg, this);
|
||||
if (_a_show.animating()) {
|
||||
_pinnedBar->cancel->hide();
|
||||
_pinnedBar->shadow->hide();
|
||||
|
@ -5261,8 +5318,8 @@ bool HistoryWidget::pinnedMsgVisibilityUpdated() {
|
|||
if (!barTop || _scroll->scrollTop() != *barTop) {
|
||||
synteticScrollToY(_scroll->scrollTop() + st::historyReplyHeight);
|
||||
}
|
||||
} else if (_pinnedBar->msgId != pinnedId) {
|
||||
_pinnedBar->msgId = pinnedId;
|
||||
} else if (_pinnedBar->msgId != pinnedId.msg) {
|
||||
_pinnedBar->msgId = pinnedId.msg;
|
||||
_pinnedBar->msg = nullptr;
|
||||
_pinnedBar->text.clear();
|
||||
updatePinnedBar();
|
||||
|
@ -5565,7 +5622,7 @@ void HistoryWidget::UnpinMessage(not_null<PeerData*> peer, MsgId msgId) {
|
|||
}
|
||||
|
||||
void HistoryWidget::hidePinnedMessage() {
|
||||
const auto pinnedId = _peer ? _peer->topPinnedMessageId() : MsgId(0);
|
||||
const auto pinnedId = _pinnedId;
|
||||
if (!pinnedId) {
|
||||
if (pinnedMsgVisibilityUpdated()) {
|
||||
updateControlsGeometry();
|
||||
|
@ -5575,11 +5632,9 @@ void HistoryWidget::hidePinnedMessage() {
|
|||
}
|
||||
|
||||
if (_peer->canPinMessages()) {
|
||||
unpinMessage(FullMsgId(
|
||||
_peer->isChannel() ? peerToChannel(_peer->id) : NoChannel,
|
||||
pinnedId));
|
||||
unpinMessage(pinnedId);
|
||||
} else {
|
||||
session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId);
|
||||
session().settings().setHiddenPinnedMessageId(_peer->id, pinnedId.msg);
|
||||
session().saveSettings();
|
||||
if (pinnedMsgVisibilityUpdated()) {
|
||||
updateControlsGeometry();
|
||||
|
@ -6389,7 +6444,9 @@ void HistoryWidget::drawPinnedBar(Painter &p) {
|
|||
p.setPen(st::historyReplyNameFg);
|
||||
p.setFont(st::msgServiceNameFont);
|
||||
const auto poll = media ? media->poll() : nullptr;
|
||||
const auto pinnedHeader = !poll
|
||||
const auto pinnedHeader = (_pinnedBar->msgId < _peer->topPinnedMessageId())
|
||||
? tr::lng_pinned_previous(tr::now)
|
||||
: !poll
|
||||
? tr::lng_pinned_message(tr::now)
|
||||
: poll->quiz()
|
||||
? tr::lng_pinned_quiz(tr::now)
|
||||
|
|
|
@ -93,6 +93,7 @@ namespace HistoryView {
|
|||
class TopBarWidget;
|
||||
class ContactStatus;
|
||||
class Element;
|
||||
class PinnedTracker;
|
||||
} // namespace HistoryView
|
||||
|
||||
class DragArea;
|
||||
|
@ -491,9 +492,12 @@ private:
|
|||
void updateReplyEditTexts(bool force = false);
|
||||
void updateReplyEditText(not_null<HistoryItem*> item);
|
||||
|
||||
void showPinnedMessage(FullMsgId id);
|
||||
void updatePinnedBar(bool force = false);
|
||||
bool pinnedMsgVisibilityUpdated();
|
||||
void destroyPinnedBar();
|
||||
void updatePinnedViewer();
|
||||
void setupPinnedTracker();
|
||||
|
||||
void sendInlineResult(
|
||||
not_null<InlineBots::Result*> result,
|
||||
|
@ -610,7 +614,9 @@ private:
|
|||
|
||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||
|
||||
FullMsgId _pinnedId;
|
||||
std::unique_ptr<PinnedBar> _pinnedBar;
|
||||
std::unique_ptr<HistoryView::PinnedTracker> _pinnedTracker;
|
||||
|
||||
mtpRequestId _saveEditMsgRequestId = 0;
|
||||
|
||||
|
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
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 "history/view/history_view_pinned_tracker.h"
|
||||
|
||||
#include "data/data_changes.h"
|
||||
#include "data/data_pinned_messages.h"
|
||||
#include "data/data_peer.h"
|
||||
#include "data/data_channel.h"
|
||||
#include "data/data_session.h"
|
||||
#include "data/data_histories.h"
|
||||
#include "main/main_session.h"
|
||||
#include "history/history.h"
|
||||
#include "history/history_item.h"
|
||||
#include "apiwrap.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
constexpr auto kLoadedLimit = 4;
|
||||
constexpr auto kPerPage = 40;
|
||||
|
||||
} // namespace
|
||||
|
||||
PinnedTracker::PinnedTracker(not_null<History*> history) : _history(history) {
|
||||
_history->session().changes().peerFlagsValue(
|
||||
_history->peer,
|
||||
Data::PeerUpdate::Flag::PinnedMessage
|
||||
) | rpl::start_with_next([=] {
|
||||
refreshData();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
PinnedTracker::~PinnedTracker() {
|
||||
_history->owner().histories().cancelRequest(_beforeRequestId);
|
||||
_history->owner().histories().cancelRequest(_afterRequestId);
|
||||
}
|
||||
|
||||
rpl::producer<MsgId> PinnedTracker::shownMessageId() const {
|
||||
return _current.value();
|
||||
}
|
||||
|
||||
void PinnedTracker::refreshData() {
|
||||
const auto now = _history->peer->currentPinnedMessages();
|
||||
if (!now) {
|
||||
_dataLifetime.destroy();
|
||||
_current = MsgId(0);
|
||||
} else if (_data.get() != now) {
|
||||
_dataLifetime.destroy();
|
||||
_data = now;
|
||||
if (_aroundId) {
|
||||
setupViewer(now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedTracker::trackAround(MsgId messageId) {
|
||||
if (_aroundId == messageId) {
|
||||
return;
|
||||
}
|
||||
_dataLifetime.destroy();
|
||||
_aroundId = messageId;
|
||||
if (!_aroundId) {
|
||||
_current = MsgId(0);
|
||||
} else if (const auto now = _data.get()) {
|
||||
setupViewer(now);
|
||||
}
|
||||
}
|
||||
|
||||
void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
|
||||
data->viewer(
|
||||
_aroundId,
|
||||
kLoadedLimit + 2
|
||||
) | rpl::start_with_next([=](const Data::PinnedAroundId &snapshot) {
|
||||
const auto i = ranges::lower_bound(snapshot.ids, _aroundId);
|
||||
const auto empty = snapshot.ids.empty();
|
||||
const auto before = (i - begin(snapshot.ids));
|
||||
const auto after = (end(snapshot.ids) - i);
|
||||
if (before < kLoadedLimit && !snapshot.skippedBefore) {
|
||||
load(
|
||||
Data::LoadDirection::Before,
|
||||
empty ? _aroundId : snapshot.ids.front());
|
||||
}
|
||||
if (after < kLoadedLimit && !snapshot.skippedAfter) {
|
||||
load(
|
||||
Data::LoadDirection::After,
|
||||
empty ? _aroundId : snapshot.ids.back());
|
||||
}
|
||||
if (i != begin(snapshot.ids)) {
|
||||
_current = *(i - 1);
|
||||
} else if (snapshot.skippedBefore == 0) {
|
||||
_current = 0;
|
||||
}
|
||||
}, _dataLifetime);
|
||||
}
|
||||
|
||||
void PinnedTracker::load(Data::LoadDirection direction, MsgId id) {
|
||||
const auto requestId = (direction == Data::LoadDirection::Before)
|
||||
? &_beforeRequestId
|
||||
: &_afterRequestId;
|
||||
const auto aroundId = (direction == Data::LoadDirection::Before)
|
||||
? &_beforeId
|
||||
: &_afterId;
|
||||
if (*requestId) {
|
||||
if (*aroundId == id) {
|
||||
return;
|
||||
}
|
||||
_history->owner().histories().cancelRequest(*requestId);
|
||||
}
|
||||
*aroundId = id;
|
||||
const auto send = [=](Fn<void()> finish) {
|
||||
const auto offsetId = [&] {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: return id;
|
||||
case Data::LoadDirection::After: return id + 1;
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::load");
|
||||
}();
|
||||
const auto addOffset = [&] {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: return 0;
|
||||
case Data::LoadDirection::After: return -kPerPage;
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::load");
|
||||
}();
|
||||
return _history->session().api().request(MTPmessages_Search(
|
||||
MTP_flags(0),
|
||||
_history->peer->input,
|
||||
MTP_string(QString()),
|
||||
MTP_inputPeerEmpty(),
|
||||
MTPint(), // top_msg_id
|
||||
MTP_inputMessagesFilterPinned(),
|
||||
MTP_int(0),
|
||||
MTP_int(0),
|
||||
MTP_int(offsetId),
|
||||
MTP_int(addOffset),
|
||||
MTP_int(kPerPage),
|
||||
MTP_int(0), // max_id
|
||||
MTP_int(0), // min_id
|
||||
MTP_int(0) // hash
|
||||
)).done([=](const MTPmessages_Messages &result) {
|
||||
*aroundId = 0;
|
||||
*requestId = 0;
|
||||
finish();
|
||||
|
||||
apply(direction, id, result);
|
||||
}).fail([=](const RPCError &error) {
|
||||
*aroundId = 0;
|
||||
*requestId = 0;
|
||||
finish();
|
||||
}).send();
|
||||
};
|
||||
_beforeRequestId = _history->owner().histories().sendRequest(
|
||||
_history,
|
||||
Data::Histories::RequestType::History,
|
||||
send);
|
||||
}
|
||||
|
||||
void PinnedTracker::apply(
|
||||
Data::LoadDirection direction,
|
||||
MsgId aroundId,
|
||||
const MTPmessages_Messages &result) {
|
||||
auto noSkipRange = MsgRange{ aroundId, aroundId };
|
||||
auto fullCount = std::optional<int>();
|
||||
auto messages = [&] {
|
||||
switch (result.type()) {
|
||||
case mtpc_messages_messages: {
|
||||
auto &d = result.c_messages_messages();
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vmessages().v.size();
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesSlice: {
|
||||
auto &d = result.c_messages_messagesSlice();
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vcount().v;
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_channelMessages: {
|
||||
auto &d = result.c_messages_channelMessages();
|
||||
if (auto channel = _history->peer->asChannel()) {
|
||||
channel->ptsReceived(d.vpts().v);
|
||||
} else {
|
||||
LOG(("API Error: received messages.channelMessages when "
|
||||
"no channel was passed! (PinnedTracker::apply)"));
|
||||
}
|
||||
_history->owner().processUsers(d.vusers());
|
||||
_history->owner().processChats(d.vchats());
|
||||
fullCount = d.vcount().v;
|
||||
return &d.vmessages().v;
|
||||
} break;
|
||||
|
||||
case mtpc_messages_messagesNotModified: {
|
||||
LOG(("API Error: received messages.messagesNotModified! "
|
||||
"(PinnedTracker::apply)"));
|
||||
return (const QVector<MTPMessage>*)nullptr;
|
||||
} break;
|
||||
}
|
||||
Unexpected("messages.Messages type in PinnedTracker::apply.");
|
||||
}();
|
||||
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto addType = NewMessageType::Existing;
|
||||
auto list = std::vector<MsgId>();
|
||||
list.reserve(messages->size());
|
||||
for (const auto &message : *messages) {
|
||||
const auto item = _history->owner().addNewMessage(
|
||||
message,
|
||||
MTPDmessage_ClientFlags(),
|
||||
addType);
|
||||
if (item) {
|
||||
const auto itemId = item->id;
|
||||
if (item->isPinned()) {
|
||||
list.push_back(itemId);
|
||||
}
|
||||
accumulate_min(noSkipRange.from, itemId);
|
||||
accumulate_max(noSkipRange.till, itemId);
|
||||
}
|
||||
}
|
||||
if (aroundId && list.empty()) {
|
||||
noSkipRange = [&]() -> MsgRange {
|
||||
switch (direction) {
|
||||
case Data::LoadDirection::Before: // All old loaded.
|
||||
return { 0, noSkipRange.till };
|
||||
case Data::LoadDirection::Around: // All loaded.
|
||||
return { 0, ServerMaxMsgId };
|
||||
case Data::LoadDirection::After: // All new loaded.
|
||||
return { noSkipRange.from, ServerMaxMsgId };
|
||||
}
|
||||
Unexpected("Direction in PinnedTracker::apply.");
|
||||
}();
|
||||
}
|
||||
_history->peer->addPinnedSlice(std::move(list), noSkipRange, fullCount);
|
||||
}
|
||||
|
||||
} // namespace HistoryView
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
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
|
||||
|
||||
class History;
|
||||
|
||||
namespace Data {
|
||||
class PinnedMessages;
|
||||
enum class LoadDirection : char;
|
||||
} // namespace Data
|
||||
|
||||
namespace HistoryView {
|
||||
|
||||
class PinnedTracker final {
|
||||
public:
|
||||
explicit PinnedTracker(not_null<History*> history);
|
||||
~PinnedTracker();
|
||||
|
||||
[[nodiscard]] rpl::producer<MsgId> shownMessageId() const;
|
||||
void trackAround(MsgId messageId);
|
||||
|
||||
private:
|
||||
void refreshData();
|
||||
void setupViewer(not_null<Data::PinnedMessages*> data);
|
||||
void load(Data::LoadDirection direction, MsgId id);
|
||||
void apply(
|
||||
Data::LoadDirection direction,
|
||||
MsgId aroundId,
|
||||
const MTPmessages_Messages &result);
|
||||
|
||||
const not_null<History*> _history;
|
||||
|
||||
base::weak_ptr<Data::PinnedMessages> _data;
|
||||
rpl::variable<MsgId> _current = MsgId();
|
||||
rpl::lifetime _dataLifetime;
|
||||
|
||||
MsgId _aroundId = 0;
|
||||
MsgId _beforeId = 0;
|
||||
MsgId _afterId = 0;
|
||||
MsgId _beforeRequestId = 0;
|
||||
MsgId _afterRequestId = 0;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace HistoryView
|
Loading…
Add table
Reference in a new issue