Add pinned messages section.

This commit is contained in:
John Preston 2020-10-20 19:29:24 +03:00
parent aefef948cd
commit fc92e3fadd
26 changed files with 1142 additions and 343 deletions

View File

@ -533,6 +533,8 @@ PRIVATE
history/view/history_view_object.h
history/view/history_view_pinned_bar.cpp
history/view/history_view_pinned_bar.h
history/view/history_view_pinned_section.cpp
history/view/history_view_pinned_section.h
history/view/history_view_pinned_tracker.cpp
history/view/history_view_pinned_tracker.h
history/view/history_view_replies_section.cpp

View File

@ -383,7 +383,6 @@ void ChannelData::setUnavailableReasons(
void ChannelData::setAvailableMinId(MsgId availableMinId) {
if (_availableMinId != availableMinId) {
_availableMinId = availableMinId;
clearPinnedMessages(_availableMinId + 1);
}
}

View File

@ -38,6 +38,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/view/history_view_element.h"
#include "history/history_item.h"
#include "storage/file_download.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "facades.h" // Ui::showPeerProfile
#include "app.h"
@ -468,14 +470,15 @@ MsgId PeerData::topPinnedMessageId() const {
void PeerData::ensurePinnedMessagesCreated() {
if (!_pinnedMessages) {
_pinnedMessages = std::make_unique<Data::PinnedMessages>(
peerToChannel(id));
_pinnedMessages = std::make_unique<Data::PinnedMessages>(this);
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
}
void PeerData::removeEmptyPinnedMessages() {
if (_pinnedMessages && _pinnedMessages->empty()) {
_pinnedMessages = nullptr;
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
}
@ -491,69 +494,24 @@ void PeerData::setTopPinnedMessageId(MsgId messageId) {
clearPinnedMessages();
return;
}
if (session().settings().hiddenPinnedMessageId(id) != messageId) {
const auto hiddenId = session().settings().hiddenPinnedMessageId(id);
if (hiddenId != 0 && hiddenId != messageId) {
session().settings().setHiddenPinnedMessageId(id, 0);
session().saveSettingsDelayed();
}
ensurePinnedMessagesCreated();
_pinnedMessages->setTopId(messageId);
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
void PeerData::clearPinnedMessages(MsgId lessThanId) {
if (lessThanId == ServerMaxMsgId
&& session().settings().hiddenPinnedMessageId(id) != 0) {
void PeerData::clearPinnedMessages() {
if (session().settings().hiddenPinnedMessageId(id) != 0) {
session().settings().setHiddenPinnedMessageId(id, 0);
session().saveSettingsDelayed();
}
if (!_pinnedMessages) {
return;
}
_pinnedMessages->clearLessThanId(lessThanId);
session().storage().remove(Storage::SharedMediaRemoveAll(
id,
Storage::SharedMediaType::Pinned));
removeEmptyPinnedMessages();
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
void PeerData::addPinnedMessage(MsgId messageId) {
if (messageIdTooSmall(messageId)) {
return;
}
ensurePinnedMessagesCreated();
_pinnedMessages->add(messageId);
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
void PeerData::addPinnedSlice(
std::vector<MsgId> &&ids,
MsgRange noSkipRange,
std::optional<int> count) {
const auto min = [&] {
if (const auto channel = asChannel()) {
return channel->availableMinId();
}
return 0;
}();
ids.erase(
ranges::remove_if(ids, [&](MsgId id) { return id <= min; }),
end(ids));
if (noSkipRange.from <= min) {
noSkipRange.from = 0;
}
if (ids.empty() && !_pinnedMessages) {
return;
}
ensurePinnedMessagesCreated();
_pinnedMessages->add(std::move(ids), noSkipRange, count);
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
void PeerData::removePinnedMessage(MsgId messageId) {
if (!_pinnedMessages) {
return;
}
_pinnedMessages->remove(messageId);
removeEmptyPinnedMessages();
session().changes().peerUpdated(this, UpdateFlag::PinnedMessage);
}
bool PeerData::canExportChatHistory() const {

View File

@ -330,13 +330,7 @@ public:
[[nodiscard]] bool canEditMessagesIndefinitely() const;
[[nodiscard]] MsgId topPinnedMessageId() const;
void setTopPinnedMessageId(MsgId messageId);
void clearPinnedMessages(MsgId lessThanId = ServerMaxMsgId);
void addPinnedMessage(MsgId messageId);
void addPinnedSlice(
std::vector<MsgId> &&ids,
MsgRange noSkipRange,
std::optional<int> count);
void removePinnedMessage(MsgId messageId);
void clearPinnedMessages();
Data::PinnedMessages *currentPinnedMessages() const {
return _pinnedMessages.get();
}

View File

@ -7,94 +7,76 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_pinned_messages.h"
namespace Data {
#include "data/data_peer.h"
#include "main/main_session.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
PinnedMessages::PinnedMessages(ChannelId channelId) : _channelId(channelId) {
namespace Data {
namespace {
constexpr auto PinnedType = Storage::SharedMediaType::Pinned;
using Storage::SharedMediaQuery;
using Storage::SharedMediaKey;
using Storage::SharedMediaResult;
} // namespace
PinnedMessages::PinnedMessages(not_null<PeerData*> peer)
: _peer(peer)
, _storage(_peer->session().storage()) {
}
bool PinnedMessages::empty() const {
return _list.empty();
return _storage.empty(SharedMediaKey(_peer->id, PinnedType, 0));
}
MsgId PinnedMessages::topId() const {
const auto slice = _list.snapshot(MessagesQuery{
.aroundId = MaxMessagePosition,
.limitBefore = 1,
.limitAfter = 1
});
return slice.messageIds.empty() ? 0 : slice.messageIds.back().fullId.msg;
const auto slice = _storage.snapshot(
SharedMediaQuery(
SharedMediaKey(_peer->id, PinnedType, ServerMaxMsgId),
1,
1));
return slice.messageIds.empty() ? 0 : slice.messageIds.back();
}
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) {
return _storage.query(
SharedMediaQuery(
SharedMediaKey(_peer->id, PinnedType, aroundId),
limit,
limit)
) | rpl::map([](const SharedMediaResult &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;
data.ids = result.messageIds | ranges::to_vector;
return data;
});
}
MessagePosition PinnedMessages::position(MsgId id) const {
return MessagePosition{
.fullId = FullMsgId(_channelId, id),
};
}
void PinnedMessages::add(MsgId messageId) {
_list.addOne(position(messageId));
}
void PinnedMessages::add(
std::vector<MsgId> &&ids,
MsgRange range,
std::optional<int> count) {
auto positions = ids | ranges::view::transform([&](MsgId id) {
return position(id);
}) | ranges::to_vector;
_list.addSlice(
std::move(positions),
MessagesRange{
.from = range.from ? position(range.from) : MinMessagePosition,
.till = position(range.till)
},
count);
}
void PinnedMessages::remove(MsgId messageId) {
_list.removeOne(position(messageId));
}
void PinnedMessages::setTopId(MsgId messageId) {
while (true) {
auto top = topId();
if (top > messageId) {
remove(top);
_storage.remove(Storage::SharedMediaRemoveOne(
_peer->id,
PinnedType,
top));
} else if (top == messageId) {
return;
} else {
break;
}
}
const auto wrapped = position(messageId);
_list.addSlice(
{ wrapped },
{ .from = wrapped, .till = MaxMessagePosition },
std::nullopt);
}
void PinnedMessages::clearLessThanId(MsgId messageId) {
_list.removeLessThan(position(messageId));
_storage.add(Storage::SharedMediaAddNew(
_peer->id,
PinnedType,
messageId));
}
} // namespace Data

View File

@ -10,6 +10,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_messages.h"
#include "base/weak_ptr.h"
namespace Storage {
class Facade;
} // namespace Storage
namespace Data {
struct PinnedAroundId {
@ -21,30 +25,18 @@ struct PinnedAroundId {
class PinnedMessages final : public base::has_weak_ptr {
public:
explicit PinnedMessages(ChannelId channelId);
explicit PinnedMessages(not_null<PeerData*> peer);
[[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,
MsgRange range,
std::optional<int> count);
void remove(MsgId messageId);
void setTopId(MsgId messageId);
void clearLessThanId(MsgId messageId);
private:
[[nodiscard]] MessagePosition position(MsgId id) const;
MessagesList _list;
ChannelId _channelId = 0;
const not_null<PeerData*> _peer;
Storage::Facade &_storage;
};

View File

@ -55,6 +55,8 @@ std::optional<MTPmessages_Search> PrepareSearchRequest(
return MTP_inputMessagesFilterUrl();
case Type::ChatPhoto:
return MTP_inputMessagesFilterChatPhotos();
case Type::Pinned:
return MTP_inputMessagesFilterPinned();
}
return MTP_inputMessagesFilterEmpty();
}();

View File

@ -123,7 +123,8 @@ rpl::producer<SparseIdsSlice> SharedMediaViewer(
using AllRemoved = Storage::SharedMediaRemoveAll;
session->storage().sharedMediaAllRemoved(
) | rpl::filter([=](const AllRemoved &update) {
return (update.peerId == key.peerId);
return (update.peerId == key.peerId)
&& (update.types.test(key.type));
}) | rpl::filter([=] {
return builder->removeAll();
}) | rpl::start_with_next(pushNextSnapshot, lifetime);

View File

@ -177,7 +177,6 @@ void History::itemVanished(not_null<HistoryItem*> item) {
&& unreadCount() > 0) {
setUnreadCount(unreadCount() - 1);
}
peer->removePinnedMessage(item->id);
}
void History::setLocalDraft(std::unique_ptr<Data::Draft> &&draft) {
@ -707,9 +706,6 @@ not_null<HistoryItem*> History::addNewToBack(
item->id,
{ from, till }));
}
if (item->isPinned()) {
item->history()->peer->addPinnedMessage(item->id);
}
}
if (item->from()->id) {
if (auto user = item->from()->asUser()) {
@ -1007,10 +1003,11 @@ void History::applyServiceChanges(
replyTo->match([&](const MTPDmessageReplyHeader &data) {
const auto id = data.vreply_to_msg_id().v;
if (item) {
item->history()->peer->addPinnedSlice(
session().storage().add(Storage::SharedMediaAddSlice(
peer->id,
Storage::SharedMediaType::Pinned,
{ id },
{ id, ServerMaxMsgId },
std::nullopt);
{ id, ServerMaxMsgId }));
}
});
}
@ -1164,7 +1161,6 @@ void History::addEdgesToSharedMedia() {
{},
{ from, till }));
}
peer->addPinnedSlice({}, { from, till }, std::nullopt);
}
void History::addOlderSlice(const QVector<MTPMessage> &slice) {
@ -1328,7 +1324,6 @@ void History::checkAddAllToUnreadMentions() {
void History::addToSharedMedia(
const std::vector<not_null<HistoryItem*>> &items) {
auto pinned = std::vector<MsgId>();
std::vector<MsgId> medias[Storage::kSharedMediaTypeCount];
for (const auto item : items) {
if (const auto types = item->sharedMediaTypes()) {
@ -1342,12 +1337,6 @@ void History::addToSharedMedia(
}
}
}
if (item->isPinned()) {
if (pinned.empty()) {
pinned.reserve(items.size());
}
pinned.push_back(item->id);
}
}
const auto from = loadedAtTop() ? 0 : minMsgId();
const auto till = loadedAtBottom() ? ServerMaxMsgId : maxMsgId();
@ -1361,7 +1350,6 @@ void History::addToSharedMedia(
{ from, till }));
}
}
peer->addPinnedSlice(std::move(pinned), { from, till }, std::nullopt);
}
void History::calculateFirstUnreadMessage() {
@ -3029,7 +3017,6 @@ void History::clear(ClearType type) {
clearSharedMedia();
clearLastKeyboard();
if (const auto channel = peer->asChannel()) {
channel->clearPinnedMessages();
//if (const auto feed = channel->feed()) { // #feed
// // Should be after resetting the _lastMessage.
// feed->historyCleared(this);

View File

@ -350,10 +350,17 @@ void HistoryItem::markMediaRead() {
void HistoryItem::setIsPinned(bool pinned) {
if (pinned) {
_flags |= MTPDmessage::Flag::f_pinned;
history()->peer->addPinnedMessage(id);
history()->session().storage().add(Storage::SharedMediaAddExisting(
history()->peer->id,
Storage::SharedMediaType::Pinned,
id,
{ id, id }));
} else {
_flags &= ~MTPDmessage::Flag::f_pinned;
history()->peer->removePinnedMessage(id);
history()->session().storage().remove(Storage::SharedMediaRemoveOne(
history()->peer->id,
Storage::SharedMediaType::Pinned,
id));
}
}

View File

@ -1409,6 +1409,9 @@ Storage::SharedMediaTypesMask HistoryMessage::sharedMediaTypes() const {
if (hasTextLinks()) {
result.set(Storage::SharedMediaType::Link);
}
if (isPinned()) {
result.set(Storage::SharedMediaType::Pinned);
}
return result;
}

View File

@ -70,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 "history/view/history_view_pinned_section.h"
#include "history/view/history_view_pinned_bar.h"
#include "history/view/media/history_view_media.h"
#include "profile/profile_block_group_members.h"
@ -108,6 +109,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/window_slide_animation.h"
#include "window/window_peer_menu.h"
#include "inline_bots/inline_results_widget.h"
#include "info/profile/info_profile_values.h" // SharedMediaCountValue.
#include "chat_helpers/emoji_suggestions_widget.h"
#include "core/crash_reports.h"
#include "core/shortcuts.h"
@ -3219,6 +3221,9 @@ void HistoryWidget::doneShow() {
}
preloadHistoryIfNeeded();
updatePinnedViewer();
if (_pinnedBar) {
_pinnedBar->finishAnimating();
}
checkHistoryActivation();
App::wnd()->setInnerFocus();
}
@ -5177,10 +5182,9 @@ void HistoryWidget::updatePinnedViewer() {
}
return std::pair(item, offset);
}();
const auto last = _history->peer->topPinnedMessageId();
const auto lessThanId = item
? (item->data()->id + (offset > 0 ? 1 : 0))
: (last + 1);
: (_history->peer->topPinnedMessageId() + 1);
_pinnedTracker->trackAround(lessThanId);
}
@ -5189,7 +5193,13 @@ void HistoryWidget::setupPinnedTracker() {
_pinnedTracker = std::make_unique<HistoryView::PinnedTracker>(_history);
_pinnedBar = nullptr;
checkPinnedBarState();
Info::Profile::SharedMediaCountValue(
_history->peer,
_migrated ? _migrated->peer.get() : nullptr,
Storage::SharedMediaType::Pinned
) | rpl::start_with_next([=] {
checkPinnedBarState();
}, _pinnedTracker->lifetime());
}
void HistoryWidget::checkPinnedBarState() {
@ -5258,7 +5268,9 @@ void HistoryWidget::checkPinnedBarState() {
) | rpl::start_with_next([=] {
const auto id = _pinnedTracker->currentMessageId();
if (id.message) {
Ui::showPeerHistory(_peer, id.message);
controller()->showSection(
HistoryView::PinnedMemento(_history, id.message));
//Ui::showPeerHistory(_peer, id.message);
}
}, _pinnedBar->lifetime());
@ -5543,8 +5555,6 @@ void HistoryWidget::UnpinMessage(not_null<PeerData*> peer, MsgId msgId) {
const auto session = &peer->session();
Ui::show(Box<ConfirmBox>(tr::lng_pinned_unpin_sure(tr::now), tr::lng_pinned_unpin(tr::now), crl::guard(session, [=] {
peer->removePinnedMessage(msgId);
Ui::hideLayer();
session->api().request(MTPmessages_UpdatePinnedMessage(
MTP_flags(MTPmessages_UpdatePinnedMessage::Flag::f_unpin),

View File

@ -0,0 +1,594 @@
/*
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_section.h"
#include "history/view/history_view_top_bar_widget.h"
#include "history/view/history_view_list_widget.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/history_item.h"
#include "boxes/confirm_box.h"
#include "ui/widgets/scroll_area.h"
#include "ui/widgets/shadow.h"
#include "ui/layers/generic_box.h"
#include "ui/item_text_options.h"
#include "ui/toast/toast.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/special_buttons.h"
#include "ui/ui_utility.h"
#include "ui/toasts/common_toasts.h"
#include "base/timer_rpl.h"
#include "apiwrap.h"
#include "window/window_session_controller.h"
#include "window/window_peer_menu.h"
#include "base/event_filter.h"
#include "base/call_delayed.h"
#include "core/file_utilities.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "data/data_channel.h"
#include "data/data_pinned_messages.h"
#include "data/data_changes.h"
#include "data/data_sparse_ids.h"
#include "data/data_shared_media.h"
#include "storage/storage_account.h"
#include "platform/platform_specific.h"
#include "lang/lang_keys.h"
#include "facades.h"
#include "app.h"
#include "styles/style_chat.h"
#include "styles/style_window.h"
#include "styles/style_info.h"
#include "styles/style_boxes.h"
#include <QtCore/QMimeData>
#include <QtGui/QGuiApplication>
namespace HistoryView {
namespace {
} // namespace
object_ptr<Window::SectionWidget> PinnedMemento::createWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
Window::Column column,
const QRect &geometry) {
if (column == Window::Column::Third) {
return nullptr;
}
auto result = object_ptr<PinnedWidget>(
parent,
controller,
_history);
result->setInternalState(geometry, this);
return result;
}
PinnedWidget::PinnedWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history)
: Window::SectionWidget(parent, controller)
, _history(history)
, _topBar(this, controller)
, _topBarShadow(this)
, _scroll(std::make_unique<Ui::ScrollArea>(this, st::historyScroll, false))
, _scrollDown(_scroll.get(), st::historyToDown) {
_topBar->setActiveChat(
_history,
TopBarWidget::Section::Pinned,
nullptr);
_topBar->move(0, 0);
_topBar->resizeToWidth(width());
_topBar->show();
_topBar->deleteSelectionRequest(
) | rpl::start_with_next([=] {
confirmDeleteSelected();
}, _topBar->lifetime());
_topBar->forwardSelectionRequest(
) | rpl::start_with_next([=] {
confirmForwardSelected();
}, _topBar->lifetime());
_topBar->clearSelectionRequest(
) | rpl::start_with_next([=] {
clearSelected();
}, _topBar->lifetime());
_topBarShadow->raise();
updateAdaptiveLayout();
subscribe(Adaptive::Changed(), [=] { updateAdaptiveLayout(); });
_inner = _scroll->setOwnedWidget(object_ptr<ListWidget>(
this,
controller,
static_cast<ListDelegate*>(this)));
_scroll->move(0, _topBar->height());
_scroll->show();
connect(_scroll.get(), &Ui::ScrollArea::scrolled, [=] { onScroll(); });
setupScrollDownButton();
}
PinnedWidget::~PinnedWidget() = default;
void PinnedWidget::setupScrollDownButton() {
_scrollDown->setClickedCallback([=] {
scrollDownClicked();
});
base::install_event_filter(_scrollDown, [=](not_null<QEvent*> event) {
if (event->type() != QEvent::Wheel) {
return base::EventFilterResult::Continue;
}
return _scroll->viewportEvent(event)
? base::EventFilterResult::Cancel
: base::EventFilterResult::Continue;
});
updateScrollDownVisibility();
}
void PinnedWidget::scrollDownClicked() {
if (QGuiApplication::keyboardModifiers() == Qt::ControlModifier) {
showAtEnd();
//} else if (_replyReturn) {
// showAtPosition(_replyReturn->position());
} else {
showAtEnd();
}
}
void PinnedWidget::showAtStart() {
showAtPosition(Data::MinMessagePosition);
}
void PinnedWidget::showAtEnd() {
showAtPosition(Data::MaxMessagePosition);
}
void PinnedWidget::showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem) {
if (!showAtPositionNow(position, originItem)) {
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, originItem);
});
}
}
bool PinnedWidget::showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem) {
const auto item = position.fullId
? _history->owner().message(position.fullId)
: nullptr;
const auto use = item ? item->position() : position;
if (const auto scrollTop = _inner->scrollTopForPosition(use)) {
const auto currentScrollTop = _scroll->scrollTop();
const auto wanted = snap(*scrollTop, 0, _scroll->scrollTopMax());
const auto fullDelta = (wanted - currentScrollTop);
const auto limit = _scroll->height();
const auto scrollDelta = snap(fullDelta, -limit, limit);
_inner->animatedScrollTo(
wanted,
use,
scrollDelta,
(std::abs(fullDelta) > limit
? HistoryView::ListWidget::AnimatedScroll::Part
: HistoryView::ListWidget::AnimatedScroll::Full));
if (use != Data::MaxMessagePosition
&& use != Data::UnreadMessagePosition) {
_inner->highlightMessage(use.fullId);
}
return true;
}
return false;
}
void PinnedWidget::updateScrollDownVisibility() {
if (animating()) {
return;
}
const auto scrollDownIsVisible = [&]() -> std::optional<bool> {
const auto top = _scroll->scrollTop() + st::historyToDownShownAfter;
if (top < _scroll->scrollTopMax()) {
return true;
} else if (_inner->loadedAtBottomKnown()) {
return !_inner->loadedAtBottom();
}
return std::nullopt;
};
const auto scrollDownIsShown = scrollDownIsVisible();
if (!scrollDownIsShown) {
return;
}
if (_scrollDownIsShown != *scrollDownIsShown) {
_scrollDownIsShown = *scrollDownIsShown;
_scrollDownShown.start(
[=] { updateScrollDownPosition(); },
_scrollDownIsShown ? 0. : 1.,
_scrollDownIsShown ? 1. : 0.,
st::historyToDownDuration);
}
}
void PinnedWidget::updateScrollDownPosition() {
// _scrollDown is a child widget of _scroll, not me.
auto top = anim::interpolate(
0,
_scrollDown->height() + st::historyToDownPosition.y(),
_scrollDownShown.value(_scrollDownIsShown ? 1. : 0.));
_scrollDown->moveToRight(
st::historyToDownPosition.x(),
_scroll->height() - top);
auto shouldBeHidden = !_scrollDownIsShown && !_scrollDownShown.animating();
if (shouldBeHidden != _scrollDown->isHidden()) {
_scrollDown->setVisible(!shouldBeHidden);
}
}
void PinnedWidget::scrollDownAnimationFinish() {
_scrollDownShown.stop();
updateScrollDownPosition();
}
void PinnedWidget::updateAdaptiveLayout() {
_topBarShadow->moveToLeft(
Adaptive::OneColumn() ? 0 : st::lineWidth,
_topBar->height());
}
not_null<History*> PinnedWidget::history() const {
return _history;
}
Dialogs::RowDescriptor PinnedWidget::activeChat() const {
return {
_history,
FullMsgId(_history->channelId(), ShowAtUnreadMsgId)
};
}
QPixmap PinnedWidget::grabForShowAnimation(const Window::SectionSlideParams &params) {
_topBar->updateControlsVisibility();
if (params.withTopBarShadow) _topBarShadow->hide();
auto result = Ui::GrabWidget(this);
if (params.withTopBarShadow) _topBarShadow->show();
return result;
}
void PinnedWidget::doSetInnerFocus() {
if (!_inner->getSelectedText().rich.text.isEmpty()
|| !_inner->getSelectedItems().empty()) {
_inner->setFocus();
}
}
bool PinnedWidget::showInternal(
not_null<Window::SectionMemento*> memento,
const Window::SectionShow &params) {
if (auto logMemento = dynamic_cast<PinnedMemento*>(memento.get())) {
if (logMemento->getHistory() == history()) {
restoreState(logMemento);
return true;
}
}
return false;
}
void PinnedWidget::setInternalState(
const QRect &geometry,
not_null<PinnedMemento*> memento) {
setGeometry(geometry);
Ui::SendPendingMoveResizeEvents(this);
restoreState(memento);
}
std::unique_ptr<Window::SectionMemento> PinnedWidget::createMemento() {
auto result = std::make_unique<PinnedMemento>(history());
saveState(result.get());
return result;
}
bool PinnedWidget::showMessage(
PeerId peerId,
const Window::SectionShow &params,
MsgId messageId) {
if (peerId != _history->peer->id) {
return false;
}
const auto id = FullMsgId{
_history->channelId(),
messageId
};
const auto message = _history->owner().message(id);
if (!message || !message->isPinned()) {
return false;
}
const auto originItem = [&]() -> HistoryItem* {
using OriginMessage = Window::SectionShow::OriginMessage;
if (const auto origin = std::get_if<OriginMessage>(&params.origin)) {
if (const auto returnTo = session().data().message(origin->id)) {
if (returnTo->history() == _history && returnTo->isPinned()) {
return returnTo;
}
}
}
return nullptr;
}();
showAtPosition(
Data::MessagePosition{ .fullId = id, .date = message->date() },
originItem);
return true;
}
void PinnedWidget::saveState(not_null<PinnedMemento*> memento) {
_inner->saveState(memento->list());
}
void PinnedWidget::restoreState(not_null<PinnedMemento*> memento) {
_inner->restoreState(memento->list());
if (const auto highlight = memento->getHighlightId()) {
const auto position = Data::MessagePosition{
.fullId = FullMsgId(_history->channelId(), highlight),
.date = TimeId(0),
};
_inner->showAroundPosition(position, [=] {
return showAtPositionNow(position, nullptr);
});
}
}
void PinnedWidget::resizeEvent(QResizeEvent *e) {
if (!width() || !height()) {
return;
}
recountChatWidth();
updateControlsGeometry();
}
void PinnedWidget::recountChatWidth() {
auto layout = (width() < st::adaptiveChatWideWidth)
? Adaptive::ChatLayout::Normal
: Adaptive::ChatLayout::Wide;
if (layout != Global::AdaptiveChatLayout()) {
Global::SetAdaptiveChatLayout(layout);
Adaptive::Changed().notify(true);
}
}
void PinnedWidget::updateControlsGeometry() {
const auto contentWidth = width();
const auto newScrollTop = _scroll->isHidden()
? std::nullopt
: base::make_optional(_scroll->scrollTop() + topDelta());
_topBar->resizeToWidth(contentWidth);
_topBarShadow->resize(contentWidth, st::lineWidth);
const auto bottom = height();
const auto controlsHeight = 0;
const auto scrollY = _topBar->height();
const auto scrollHeight = bottom - scrollY - controlsHeight;
const auto scrollSize = QSize(contentWidth, scrollHeight);
if (_scroll->size() != scrollSize) {
_skipScrollEvent = true;
_scroll->resize(scrollSize);
_inner->resizeToWidth(scrollSize.width(), _scroll->height());
_skipScrollEvent = false;
}
_scroll->move(0, scrollY);
if (!_scroll->isHidden()) {
if (newScrollTop) {
_scroll->scrollToY(*newScrollTop);
}
updateInnerVisibleArea();
}
updateScrollDownPosition();
}
void PinnedWidget::paintEvent(QPaintEvent *e) {
if (animating()) {
SectionWidget::paintEvent(e);
return;
} else if (Ui::skipPaintEvent(this, e)) {
return;
}
const auto aboveHeight = _topBar->height();
const auto bg = e->rect().intersected(
QRect(0, aboveHeight, width(), height() - aboveHeight));
SectionWidget::PaintBackground(controller(), this, bg);
}
void PinnedWidget::onScroll() {
if (_skipScrollEvent) {
return;
}
updateInnerVisibleArea();
}
void PinnedWidget::updateInnerVisibleArea() {
const auto scrollTop = _scroll->scrollTop();
_inner->setVisibleTopBottom(scrollTop, scrollTop + _scroll->height());
updateScrollDownVisibility();
}
void PinnedWidget::showAnimatedHook(
const Window::SectionSlideParams &params) {
_topBar->setAnimatingMode(true);
if (params.withTopBarShadow) {
_topBarShadow->show();
}
}
void PinnedWidget::showFinishedHook() {
_topBar->setAnimatingMode(false);
}
bool PinnedWidget::floatPlayerHandleWheelEvent(QEvent *e) {
return _scroll->viewportEvent(e);
}
QRect PinnedWidget::floatPlayerAvailableRect() {
return mapToGlobal(_scroll->geometry());
}
Context PinnedWidget::listContext() {
return Context::Replies;
}
void PinnedWidget::listScrollTo(int top) {
if (_scroll->scrollTop() != top) {
_scroll->scrollToY(top);
} else {
updateInnerVisibleArea();
}
}
void PinnedWidget::listCancelRequest() {
if (_inner && !_inner->getSelectedItems().empty()) {
clearSelected();
return;
}
controller()->showBackFromStack();
}
void PinnedWidget::listDeleteRequest() {
confirmDeleteSelected();
}
rpl::producer<Data::MessagesSlice> PinnedWidget::listSource(
Data::MessagePosition aroundId,
int limitBefore,
int limitAfter) {
const auto channelId = peerToChannel(_history->peer->id);
const auto messageId = aroundId.fullId.msg
? aroundId.fullId.msg
: (ServerMaxMsgId - 1);
return SharedMediaViewer(
&_history->session(),
Storage::SharedMediaKey(
_history->peer->id,
Storage::SharedMediaType::Pinned,
messageId),
limitBefore,
limitAfter
) | rpl::map([=](SparseIdsSlice &&slice) {
auto result = Data::MessagesSlice();
result.fullCount = slice.fullCount();
result.skippedAfter = slice.skippedAfter();
result.skippedBefore = slice.skippedBefore();
const auto count = slice.size();
result.ids.reserve(count);
if (const auto msgId = slice.nearest(messageId)) {
result.nearestToAround = FullMsgId(channelId, *msgId);
}
for (auto i = 0; i != count; ++i) {
result.ids.emplace_back(channelId, slice[i]);
}
return result;
});
}
bool PinnedWidget::listAllowsMultiSelect() {
return true;
}
bool PinnedWidget::listIsItemGoodForSelection(
not_null<HistoryItem*> item) {
return !item->isSending() && !item->hasFailed();
}
bool PinnedWidget::listIsLessInOrder(
not_null<HistoryItem*> first,
not_null<HistoryItem*> second) {
return first->position() < second->position();
}
void PinnedWidget::listSelectionChanged(SelectedItems &&items) {
HistoryView::TopBarWidget::SelectedState state;
state.count = items.size();
for (const auto item : items) {
if (item.canDelete) {
++state.canDeleteCount;
}
if (item.canForward) {
++state.canForwardCount;
}
}
_topBar->showSelected(state);
}
void PinnedWidget::listVisibleItemsChanged(HistoryItemsList &&items) {
}
MessagesBarData PinnedWidget::listMessagesBar(
const std::vector<not_null<Element*>> &elements) {
return {};
}
void PinnedWidget::listContentRefreshed() {
}
ClickHandlerPtr PinnedWidget::listDateLink(not_null<Element*> view) {
return nullptr;
}
bool PinnedWidget::listElementHideReply(not_null<const Element*> view) {
return false;
}
bool PinnedWidget::listElementShownUnread(not_null<const Element*> view) {
return view->data()->unread();
}
bool PinnedWidget::listIsGoodForAroundPosition(
not_null<const Element*> view) {
return IsServerMsgId(view->data()->id);
}
void PinnedWidget::confirmDeleteSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {
return;
}
const auto weak = Ui::MakeWeak(this);
const auto box = Ui::show(Box<DeleteMessagesBox>(
&_history->session(),
std::move(items)));
box->setDeleteConfirmedCallback([=] {
if (const auto strong = weak.data()) {
strong->clearSelected();
}
});
}
void PinnedWidget::confirmForwardSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {
return;
}
const auto weak = Ui::MakeWeak(this);
Window::ShowForwardMessagesBox(controller(), std::move(items), [=] {
if (const auto strong = weak.data()) {
strong->clearSelected();
}
});
}
void PinnedWidget::clearSelected() {
_inner->cancelSelection();
}
} // namespace HistoryView

View File

@ -0,0 +1,182 @@
/*
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"
#include "base/weak_ptr.h"
#include "base/timer.h"
class History;
namespace Ui {
class ScrollArea;
class PlainShadow;
class FlatButton;
class HistoryDownButton;
} // namespace Ui
namespace Profile {
class BackButton;
} // namespace Profile
namespace HistoryView {
class Element;
class TopBarWidget;
class PinnedMemento;
class PinnedWidget final
: public Window::SectionWidget
, private ListDelegate {
public:
PinnedWidget(
QWidget *parent,
not_null<Window::SessionController*> controller,
not_null<History*> history);
~PinnedWidget();
[[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;
bool showMessage(
PeerId peerId,
const Window::SectionShow &params,
MsgId messageId) override;
void setInternalState(
const QRect &geometry,
not_null<PinnedMemento*> memento);
// 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;
MessagesBarData listMessagesBar(
const std::vector<not_null<Element*>> &elements) override;
void listContentRefreshed() override;
ClickHandlerPtr listDateLink(not_null<Element*> view) override;
bool listElementHideReply(not_null<const Element*> view) override;
bool listElementShownUnread(not_null<const Element*> view) override;
bool listIsGoodForAroundPosition(not_null<const 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<PinnedMemento*> memento);
void restoreState(not_null<PinnedMemento*> memento);
void showAtStart();
void showAtEnd();
void showAtPosition(
Data::MessagePosition position,
HistoryItem *originItem = nullptr);
bool showAtPositionNow(
Data::MessagePosition position,
HistoryItem *originItem);
void setupScrollDownButton();
void scrollDownClicked();
void scrollDownAnimationFinish();
void updateScrollDownVisibility();
void updateScrollDownPosition();
void confirmDeleteSelected();
void confirmForwardSelected();
void clearSelected();
void recountChatWidth();
const not_null<History*> _history;
QPointer<ListWidget> _inner;
object_ptr<TopBarWidget> _topBar;
object_ptr<Ui::PlainShadow> _topBarShadow;
bool _skipScrollEvent = false;
std::unique_ptr<Ui::ScrollArea> _scroll;
Ui::Animations::Simple _scrollDownShown;
bool _scrollDownIsShown = false;
object_ptr<Ui::HistoryDownButton> _scrollDown;
Data::MessagesSlice _lastSlice;
};
class PinnedMemento : public Window::SectionMemento {
public:
PinnedMemento(
not_null<History*> history,
MsgId highlightId = 0)
: _history(history)
, _highlightId(highlightId) {
}
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]] not_null<ListMemento*> list() {
return &_list;
}
[[nodiscard]] MsgId getHighlightId() const {
return _highlightId;
}
private:
const not_null<History*> _history;
const MsgId _highlightId = 0;
ListMemento _list;
};
} // namespace HistoryView

View File

@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h"
#include "data/data_histories.h"
#include "main/main_session.h"
#include "storage/storage_facade.h"
#include "storage/storage_shared_media.h"
#include "history/history.h"
#include "history/history_item.h"
#include "apiwrap.h"
@ -88,16 +90,16 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
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 (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 (snapshot.ids.empty()) {
_current = PinnedId();
return;
@ -117,151 +119,156 @@ void PinnedTracker::setupViewer(not_null<Data::PinnedMessages*> data) {
}
}, _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);
}
//
//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->session().storage().add(Storage::SharedMediaAddSlice(
// _history->peer->id,
// Storage::SharedMediaType::Pinned,
// std::move(list),
// noSkipRange,
// fullCount));
//}
} // namespace HistoryView

View File

@ -41,14 +41,18 @@ public:
void trackAround(MsgId messageId);
void reset();
[[nodiscard]] rpl::lifetime &lifetime() {
return _lifetime;
}
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);
//void load(Data::LoadDirection direction, MsgId id);
//void apply(
// Data::LoadDirection direction,
// MsgId aroundId,
// const MTPmessages_Messages &result);
const not_null<History*> _history;

View File

@ -176,10 +176,6 @@ RepliesWidget::RepliesWidget(
_rootView->move(0, _topBar->height());
_topBar->sendNowSelectionRequest(
) | rpl::start_with_next([=] {
confirmSendNowSelected();
}, _topBar->lifetime());
_topBar->deleteSelectionRequest(
) | rpl::start_with_next([=] {
confirmDeleteSelected();
@ -1736,19 +1732,6 @@ bool RepliesWidget::listIsGoodForAroundPosition(
return IsServerMsgId(view->data()->id);
}
void RepliesWidget::confirmSendNowSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {
return;
}
const auto navigation = controller();
Window::ShowSendNowMessagesBox(
navigation,
_history,
std::move(items),
[=] { navigation->showBackFromStack(); });
}
void RepliesWidget::confirmDeleteSelected() {
auto items = _inner->getSelectedItems();
if (items.empty()) {

View File

@ -168,7 +168,6 @@ private:
void updateScrollDownPosition();
void updatePinnedVisibility();
void confirmSendNowSelected();
void confirmDeleteSelected();
void confirmForwardSelected();
void clearSelected();

View File

@ -335,12 +335,15 @@ void TopBarWidget::paintTopBar(Painter &p) {
const auto folder = _activeChat.folder();
if (folder
|| history->peer->sharedMediaInfo()
|| (_section == Section::Scheduled)) {
|| (_section == Section::Scheduled)
|| (_section == Section::Pinned)) {
// #TODO feed name emoji.
auto text = (_section == Section::Scheduled)
? ((history && history->peer->isSelf())
? tr::lng_reminder_messages(tr::now)
: tr::lng_scheduled_messages(tr::now))
: (_section == Section::Pinned)
? "Pinned messages" // #TODO pinned
: folder
? folder->chatListName()
: history->peer->isSelf()

View File

@ -46,6 +46,7 @@ public:
enum class Section {
History,
Scheduled,
Pinned,
Replies,
};

View File

@ -22,6 +22,8 @@ public:
void remove(SharedMediaRemoveAll &&query);
void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
bool empty(const SharedMediaKey &key) const;
rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const;
@ -83,6 +85,14 @@ rpl::producer<SharedMediaResult> Facade::Impl::query(SharedMediaQuery &&query) c
return _sharedMedia.query(std::move(query));
}
SharedMediaResult Facade::Impl::snapshot(const SharedMediaQuery &query) const {
return _sharedMedia.snapshot(query);
}
bool Facade::Impl::empty(const SharedMediaKey &key) const {
return _sharedMedia.empty(key);
}
rpl::producer<SharedMediaSliceUpdate> Facade::Impl::sharedMediaSliceUpdated() const {
return _sharedMedia.sliceUpdated();
}
@ -203,6 +213,14 @@ rpl::producer<SharedMediaResult> Facade::query(SharedMediaQuery &&query) const {
return _impl->query(std::move(query));
}
SharedMediaResult Facade::snapshot(const SharedMediaQuery &query) const {
return _impl->snapshot(query);
}
bool Facade::empty(const SharedMediaKey &key) const {
return _impl->empty(key);
}
rpl::producer<SharedMediaSliceUpdate> Facade::sharedMediaSliceUpdated() const {
return _impl->sharedMediaSliceUpdated();
}

View File

@ -25,6 +25,7 @@ struct SharedMediaRemoveOne;
struct SharedMediaRemoveAll;
struct SharedMediaInvalidateBottom;
struct SharedMediaQuery;
struct SharedMediaKey;
using SharedMediaResult = SparseIdsListResult;
struct SharedMediaSliceUpdate;
@ -58,6 +59,8 @@ public:
void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
bool empty(const SharedMediaKey &key) const;
rpl::producer<SharedMediaSliceUpdate> sharedMediaSliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> sharedMediaOneRemoved() const;
rpl::producer<SharedMediaRemoveAll> sharedMediaAllRemoved() const;

View File

@ -82,7 +82,10 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
auto peerIt = _lists.find(query.peerId);
if (peerIt != _lists.end()) {
for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
peerIt->second[index].removeAll();
auto type = static_cast<SharedMediaType>(index);
if (query.types.test(type)) {
peerIt->second[index].removeAll();
}
}
_allRemoved.fire(std::move(query));
}
@ -100,6 +103,7 @@ void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) const {
Expects(IsValidSharedMediaType(query.key.type));
auto peerIt = _lists.find(query.key.peerId);
if (peerIt != _lists.end()) {
auto index = static_cast<int>(query.key.type);
@ -114,6 +118,31 @@ rpl::producer<SharedMediaResult> SharedMedia::query(SharedMediaQuery &&query) co
};
}
SharedMediaResult SharedMedia::snapshot(const SharedMediaQuery &query) const {
Expects(IsValidSharedMediaType(query.key.type));
auto peerIt = _lists.find(query.key.peerId);
if (peerIt != _lists.end()) {
auto index = static_cast<int>(query.key.type);
return peerIt->second[index].snapshot(SparseIdsListQuery(
query.key.messageId,
query.limitBefore,
query.limitAfter));
}
return {};
}
bool SharedMedia::empty(const SharedMediaKey &key) const {
Expects(IsValidSharedMediaType(key.type));
auto peerIt = _lists.find(key.peerId);
if (peerIt != _lists.end()) {
auto index = static_cast<int>(key.type);
return peerIt->second[index].empty();
}
return true;
}
rpl::producer<SharedMediaSliceUpdate> SharedMedia::sliceUpdated() const {
return _sliceUpdated.events();
}

View File

@ -26,6 +26,7 @@ enum class SharedMediaType : signed char {
RoundVoiceFile,
GIF,
RoundFile,
Pinned,
kCount,
};
@ -106,10 +107,15 @@ struct SharedMediaRemoveOne {
};
struct SharedMediaRemoveAll {
SharedMediaRemoveAll(PeerId peerId) : peerId(peerId) {
SharedMediaRemoveAll(
PeerId peerId,
SharedMediaTypesMask types = SharedMediaTypesMask::All())
: peerId(peerId)
, types(types) {
}
PeerId peerId = 0;
SharedMediaTypesMask types;
};
@ -191,6 +197,8 @@ public:
void invalidate(SharedMediaInvalidateBottom &&query);
rpl::producer<SharedMediaResult> query(SharedMediaQuery &&query) const;
SharedMediaResult snapshot(const SharedMediaQuery &query) const;
bool empty(const SharedMediaKey &key) const;
rpl::producer<SharedMediaSliceUpdate> sliceUpdated() const;
rpl::producer<SharedMediaRemoveOne> oneRemoved() const;
rpl::producer<SharedMediaRemoveAll> allRemoved() const;

View File

@ -200,6 +200,35 @@ rpl::producer<SparseIdsListResult> SparseIdsList::query(
};
}
SparseIdsListResult SparseIdsList::snapshot(
const SparseIdsListQuery &query) const {
auto slice = query.aroundId
? ranges::lower_bound(
_slices,
query.aroundId,
std::less<>(),
[](const Slice &slice) { return slice.range.till; })
: _slices.end();
if (slice != _slices.end()
&& slice->range.from <= query.aroundId) {
return queryFromSlice(query, *slice);
} else if (_count) {
auto result = SparseIdsListResult{};
result.count = _count;
return result;
}
return {};
}
bool SparseIdsList::empty() const {
for (const auto &slice : _slices) {
if (!slice.messages.empty()) {
return false;
}
}
return true;
}
rpl::producer<SparseIdsSliceUpdate> SparseIdsList::sliceUpdated() const {
return _sliceUpdated.events();
}

View File

@ -51,6 +51,8 @@ public:
void invalidateBottom();
rpl::producer<SparseIdsListResult> query(SparseIdsListQuery &&query) const;
rpl::producer<SparseIdsSliceUpdate> sliceUpdated() const;
SparseIdsListResult snapshot(const SparseIdsListQuery &query) const;
bool empty() const;
private:
struct Slice {